* Created on Apr 24, 2004
 *
 * FullHuffmanApplet
 * It takes textual commands in a TextArea and when the user
 * clicks on the Execute button, it processes the commands,
 * updating the display as it goes.
 * 
 * The program demonstrates the creation and use of a Huffman Code.
 * It illustrates the whole process, including construction of the
 * Huffman trees using a priority queue implemented as a binary heap.
 * The compression ratio is shown at the end of the history window.
 * 
 * Here is a sample command sequence:
 */
   
 /*
DELAY 100 ; pause 100 ms between updates.
BEGIN-TEXT
This is a message
that is about two lines long.
END-TEXT
FREQUENCY-TABLE
CREATE-CODE
ENCODE
DECODE
STATS
 */

/**
 * @author Steve Tanimoto, Copyright, 2004.
 *
 */ 
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;

public class FullHuffmanApplet extends JApplet implements ActionListener, Runnable {
    
	ScrolledPanel visPanel; //Where to paint graphics
	MyScrollPane msp;
	Button executeButton; 
	Button historyButton;
	TextArea userInputText;
	TextArea history;
	JFrame historyFrame;
	JTextField statusLine;
	HuffmanHeap theHeap; // The data structure being demonstrated
	String sourceText; // text used to build code or to be encoded
	String cipherText; // Huffman-coded text.
	FreqTable theTable; // Used to initialize the Huffman heap.
	CodingTable theCodingTable; // Represents the code in a form for encoding.
	DecodingTable theDecodingTable; // Represents the code in a form for decoding.
	double rawRatio = 0.0; // compression ratio, ignoring code table.
	double fullRatio = 0.0; // compression ratio, including code table.
	
	Font headingFont, keyFont, objectFont, indexFont, treeFont;
	int cellHeight = 20; // For drawing the heap.
	int cellWidth = 30; // How wide to plot pink rectangles
	int cellGap = 4; // Horizonal space between successive cells
	int topMargin = 25; // Space above top of heap.
	int fontSize = 16; // Height of font for displaying heap elemens.
	int leftMargin = 20; // x value for left side of array.
	int rightMargin = leftMargin;
	int yTreeTops = topMargin + 150; // y coord of top of trees.
	int bottomMargin = 10; // Minimum space betw. bot. of visPanel and bot. of lowest cell.
	int leftOffset = 5; // space between left side of cell and contents string.
	int delay = 300; // default is to wait 300 ms between updates.
	Thread displayThread = null;

// For HuffmanTree display:
   int nodeHeight = 15; // For drawing the nodes.
   int nodeWidth = 40; // How wide to plot pink rectangles
   int nodeVGap = 5; // vertical space between successive nodes
   int nodeHGap = 10; // horizontal space between successive nodes
   int nodeHorizSpacing = nodeWidth + nodeHGap;
   int nodeVertSpacing = nodeHeight + nodeVGap;
   int interTreeGap = 20; // horizontal space between trees.
   int ycentering = (nodeHeight - fontSize) / 2;
   static int m; // variable used when computing columns for tree layouts.

	public void init() {
		setSize(700,400); // default size of applet.
		visPanel = new ScrolledPanel();
		visPanel.setPreferredSize(new Dimension(400,400));
		msp = new MyScrollPane(visPanel);
		msp.setPreferredSize(new Dimension(400,200));
		
		Container c = getContentPane();
		c.setLayout(new BorderLayout());
		c.add(msp, BorderLayout.CENTER);
		JPanel buttons = new JPanel();
		buttons.setLayout(new FlowLayout());
		JPanel controls = new JPanel();
		controls.setLayout(new BorderLayout());
		executeButton = new Button("Execute");
		executeButton.addActionListener(this);
		buttons.add(executeButton);
		historyButton = new Button("History");
		historyButton.addActionListener(this);
		buttons.add(historyButton);
		userInputText = new TextArea(";Enter commands here.\n" +
		"DELAY 100 ; pause 100 ms between updates.\n" +
		"BEGIN-TEXT\n" +
		"This is a message\n" +
		"that is about two lines long.\n" +
		"END-TEXT\n" +
		"FREQUENCY-TABLE ; Build a table giving the number of occurrences of each char.\n" +
		"CREATE-CODE ; Create an internal hash table mapping characters to binary codewords.\n" +
		" ; This also creates a hash table going the other way, for decoding.\n" +
		"ENCODE ; translate the previously given text into its Huffman encoding.\n" +
		"DECODE ; translate the Huffman coding back to the standard representation.\n" +
		"STATS ; print out the compression ratio, etc.\n");
		statusLine = new JTextField();
		statusLine.setBackground(Color.lightGray);
		controls.add(buttons, BorderLayout.WEST);
		controls.add(userInputText, BorderLayout.CENTER);
		controls.add(statusLine, BorderLayout.SOUTH);
		controls.setPreferredSize(new Dimension(400,100));
		c.add(controls, BorderLayout.SOUTH);
		c.validate();
		
		theHeap = new HuffmanHeap();
		theHeap.init();
		
		headingFont = new Font("Helvetica", Font.PLAIN, 20);
		keyFont = new Font("Helvetica", Font.BOLD, 16);
		objectFont = new Font("Helvetica", Font.PLAIN, 20);
		indexFont = new Font("Helvetica", Font.PLAIN, 14);
		treeFont = new Font("Courier", Font.PLAIN, 14);
		history = new TextArea("FullHuffmanApplet history:\n", 20, 40); 
	}

	class ScrolledPanel extends JPanel {
		public void paintComponent(Graphics g) {
			super.paintComponent(g);
			//paintGrid(g);
			paintHeap(g);
			paintTrees(g);
		}
	}
	class MyScrollPane extends JScrollPane {
		MyScrollPane(JPanel p) {
			super(p,
				JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
				JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
		}
	}

	/**
	 * This inner class implements a priority queue ADT
	 * for data elements that are Huffman trees.
	 */
	class HuffmanHeap {

		int n; // number of elements in the heap
		int ninserts; // number of INSERT operations so far.
		int ndeletemins; // number of DELETEMIN operations so far.
		int nfindmins; // number of FINDMIN operations so far.
		int npercups; // number of calls to percolateUp so far.
		int npercdowns; // number of calls to percolateDown so far.
		int A[]; // Holds the key (priority) values.
		Object B[]; // Holds the Huffman trees (references to roots).
		
		final static int MAXSIZE = 256;
		
		HuffmanHeap(int finalSize) {
			A = new int[finalSize+1];
			B = new Object[finalSize+1];
			n = finalSize;
		}
		HuffmanHeap() {};
		void init() { // Not used in this program (was used in Program 2).
			n = 0; ninserts = 0; ndeletemins = 0; nfindmins = 0;
			npercups = 0; npercdowns = 0;
			A = new int[MAXSIZE+1];
			B = new Object[MAXSIZE+1];
		}
		int parent(int i) { return (int) Math.floor(i / 2); }
		int leftChild(int i) { return i*2; }
		int rightChild(int i) { return i*2 + 1; }
		
		String insert(Object theObject, int key) {
			if (n == MAXSIZE) {
				return "Error: the heap is full.";
			}
			n++;
			percolateUp(n, key, theObject);
			ninserts++;
			return "Inserting " + key + " and data " +
				theObject + " at position " + n;
		}

		void deletemin() {
			if (n == 0) { return; }
			ndeletemins++;
			A[1] = A[n]; 
			B[1] = B[n]; 
			n--;
			percolateDown(1);
		}
		int findmin() {
			nfindmins++;
			if (n > 0) { return A[1]; }
			else { return 9999999; }
		}
		Object findminObject() {
			nfindmins++;
			if (n > 0) { return B[1]; }
			else { return null; }
		}
		void buildHeap() {
			for (int i = n / 2; i>0; i--) {
				percolateDown(i);
			}
		}
		void percolateUp(int i, int x, Object b) {
			npercups++;
			if (x < A[parent(i)]) {
				A[i] = A[parent(i)];
				B[i] = B[parent(i)];
				updateDisplay();
				percolateUp(parent(i), x, b);
			}
			else {
				A[i] = x;
				B[i] = b;
			}
		}
		void percolateDown(int i) {
			npercdowns++;
			if (2*i > n) return;
			if (2*i == n) {
				if (A[i] > A[2*i]) {
					int tempA = A[2*i];
					Object tempB = B[2*i];
					A[2*i] = A[i];
					B[2*i] = B[i];
					A[i] = tempA;
					B[i] = tempB;
				}
			}
			else {
				int c = 2*i;
				if (A[c] > A[c+1]) { c = c+1; }
				if (A[i] > A[c]) {
					int tempA = A[c];
					Object tempB = B[c];
					A[c] = A[i];
					B[c] = B[i];
					A[i] = tempA;
					B[i] = tempB;
				}
				percolateDown(c);
			}
		}
		boolean isEmpty() { return n == 0; }
	}
	public void actionPerformed(ActionEvent e) {
		if (e.getActionCommand().equals("Execute")) {
			displayThread = new Thread(this);
			displayThread.start();
			return;
		}

		if (e.getActionCommand().equals("History")) {
			if (historyFrame == null) { 
				historyFrame = new JFrame("History of the FullHuffmanApplet");
				history.setFont(new Font("Courier", Font.PLAIN, 12));
				historyFrame.getContentPane().add(history);
				historyFrame.setSize(new Dimension(300,300));
			}
			historyFrame.show();
		}
	}
	
	// The following is executed by a separate thread for the display.
	public void run() {
		String commands = userInputText.getText();
		String line = "";
		StringTokenizer lines;
		for (lines = new StringTokenizer(commands, "\n\r\f");
				lines.hasMoreTokens();) {
			line = lines.nextToken();
			process(line, lines);
		}
		userInputText.setText(""); // Erase all the processed input.
	}
	
	// Helper function called by the run method above:
	void process(String command, StringTokenizer lines) {
		String arg1 = ""; String arg2 = "";
		StringTokenizer st = new StringTokenizer(command);
		if (! st.hasMoreTokens()) { return; }
		String firstToken = st.nextToken();
		if (firstToken.startsWith(";")) { return; }
		history.appendText(command + "\n");
		statusLine.setText(command);
		if (firstToken.equals("RESET")) { 
			theHeap = new HuffmanHeap();
			theHeap.init();
			updateDisplay(); return;			
		}
		if (firstToken.equals("SIZE")) { 
			String stats = "Current number of elements: " + theHeap.n;
			statusLine.setText(stats);
			history.appendText("; " + stats + "\n");
			return;
		}
		if (firstToken.equals("ISEMPTY")) { 
			String stats = "The heap is ";
			if (! theHeap.isEmpty()) { stats += "not "; }
			stats += "empty.";
			statusLine.setText(stats);
			history.appendText("; " + stats + "\n");
			return;
		}
		if (firstToken.equals("STATS")) { 
			String stats;
			if (theCodingTable == null) {
				report("No code has been created or used.");
				return;
			}
			stats = "Coding table size (8 bits/symbol + 2 bits/coding bit) = " + 
			theCodingTable.computeTableNBits() + " bits.";
			report(stats);
			stats = "Source text size = " + sourceText.length() +
				" bytes (or " + (sourceText.length()*8) + " bits).";
			report(stats);
			stats = "Ciphertext size = " + cipherText.length() +
				" bits.";
			report(stats);
			stats = "Size of ciphertext and table combined = " +
				(cipherText.length() + theCodingTable.tableNBits);
			report(stats);
			stats = "Compression ratio ignoring table itself = " + rawRatio;
			report(stats);
			stats = "Full compression ratio, considering the table = " + fullRatio;
			report(stats);
			return;
		}
		if (firstToken.equals("DELAY")) { 
			if (st.hasMoreTokens()) { 
				arg1 = st.nextToken();
				try { delay =(new Integer(arg1)).intValue(); }
				catch(NumberFormatException e) {
					delay = 0;
				}
				statusLine.setText("delay = " + delay);
			}
			history.appendText("; delay is now " + delay + "\n");
			return;
		}
		if (firstToken.equals("CREATE-LEAF")) { 
			arg1 = "UNDEFINED ELEMENT";
			if (st.hasMoreTokens()) { arg1 = st.nextToken(); }
			if (st.hasMoreTokens()) { arg2 = st.nextToken(); }
			int f = (new Integer(arg2)).intValue();
			HuffmanTree ht = new HuffmanTree(arg1, f);
			theHeap.insert(ht, f);
			checkScrolledPanelSize();
			updateDisplay(); return;			
		}
		if (firstToken.equals("INSERT")) { 
			arg1 = "UNDEFINED ELEMENT";
			if (st.hasMoreTokens()) { arg1 = st.nextToken(); }
			arg2 = "UNDEFINED ELEMENT";
			if (st.hasMoreTokens()) { arg2 = st.nextToken(); }
			theHeap.insert(arg1, getInt(arg2));
			checkScrolledPanelSize();
			updateDisplay(); 
			return;			
		}
		if (firstToken.equals("DELETEMIN")) { 
			theHeap.deletemin();
			updateDisplay(); return;			
		}
		if (firstToken.equals("TEST")) { 
			theTable = new FreqTable(100);
			theTable.fillTable("This is a test of the table stuff");
			theTable.show();
			frequenciesToForest(theTable);
			updateDisplay();
			theHeap.buildHeap();
			mergeAll();
			updateDisplay(); return;			
		}
		if (firstToken.equals("MERGEALL")) { 
			mergeAll();
			updateDisplay(); return;
		}
		if (firstToken.equals("FINDMIN") ||
			firstToken.equals("MINHTREE")) {
			theHeap.findmin();
			String minReport = "";
			if (theHeap.n == 0) {
				minReport = "The priority queue is empty.";
			} 
			else {minReport = "Minimum key = " + theHeap.A[1] +
				"; Data element = " + theHeap.B[1];
			} 
			statusLine.setText(minReport);
			history.appendText("; " + minReport + "\n");
			updateDisplay(); return;			
		}
		if (firstToken.equals("BEGIN-TEXT")) { 
			sourceText = "";
			command = lines.nextToken() + "\n";
			history.appendText(command);
			while (! command.startsWith("END-TEXT")) {
				sourceText += command;
				command = lines.nextToken() + "\n";
				history.appendText(command);
			}
			updateDisplay(); return;
		}
		if (firstToken.equals("FREQUENCY-TABLE")) { 
			theTable = new FreqTable(100);
			theTable.fillTable(sourceText);
			theTable.show();
			updateDisplay(); return;
		}
		if (firstToken.equals("CREATE-CODE")) { 
			frequenciesToForest(theTable);
			updateDisplay();
			theHeap.buildHeap();
			mergeAll();
			theCodingTable = new CodingTable((HuffmanTree)theHeap.B[1]);
			theCodingTable.show();
			theDecodingTable = new DecodingTable();
			theDecodingTable.init(theCodingTable);
			theDecodingTable.show();
			return;
		}
		if (firstToken.equals("ENCODE")) { 
			cipherText = "";
			for(int i = 0; i < sourceText.length(); i++) {
				cipherText += (String)theCodingTable.get(sourceText.substring(i,i+1));
			}
			report("Ciphertext is:");
			report(cipherText);
			rawRatio = ((cipherText.length() + 0.0) / (sourceText.length() + 0.0))/8.0;
			fullRatio = (cipherText.length() + theCodingTable.computeTableNBits()) / (sourceText.length() * 8.0);
			updateDisplay(); return;
		}
		if (firstToken.equals("DECODE")) { 
			sourceText = "";
			String codeWord = "";
			for(int i = 0; i < cipherText.length(); i++) {
				codeWord += cipherText.substring(i,i+1);
				String symbol = (String)theDecodingTable.get(codeWord);
				if (symbol != null) {
					sourceText += symbol;
					codeWord = "";
				}
			}
			report("Sourcetext is:");
			report(sourceText);
			updateDisplay(); return;
		}
		history.appendText("[Unknown Heap command]\n");
		statusLine.setText("Unknown Heap command: " + command);		
	}
	
	// Here is a "middleman" method that updates the display waiting with
	// the current time delay after each repaint request.
	void updateDisplay() {
		visPanel.repaint();
		if (delay > 0) {
			try { 
				Thread.sleep(delay);
			}
			catch(InterruptedException e) {}
		}
	}
	int getInt(String s) {
		int n = 1;
		try {
			Integer I = new Integer(s);
			n = I.intValue();
		}
		catch(Exception e) { n = 1; }
		return n;
	}
	/*
	 * Graphics method to actually draw the heap.
	 * It's called by the ScrolledPanel paintComponent method.
	 */
	void paintHeap(Graphics g) {
		g.setFont(headingFont);
		g.setColor(Color.BLACK);
		g.drawString( "Priority Queue of Huffman Trees", leftMargin,20);	
		int ystart = topMargin;
		int ycentering = (cellHeight - fontSize) / 2;
		int ypos = ystart;
		int xpos = leftMargin;
		FontMetrics fm = null;
		for (int i = 0; i <= theHeap.n; i++) {
			g.setColor(Color.pink);
			g.fillRect(xpos, ypos, cellWidth, 2*cellHeight);
			g.setColor(Color.black);
			g.setFont(keyFont);
			String key = theHeap.A[i]+"";
			fm = g.getFontMetrics();
			int keyWidth = fm.stringWidth(key);
			// Center the strings in the cells:
			g.drawString(key, (2*xpos + cellWidth - keyWidth)/2, ypos+cellHeight - ycentering);			
			g.setFont(objectFont);
			String contents = theHeap.B[i]+"";
			fm = g.getFontMetrics();
			int contentsWidth = fm.stringWidth(contents);
			g.drawString(contents, (2*xpos + cellWidth - contentsWidth)/2, ypos+2*cellHeight - ycentering);			
			g.setFont(indexFont);
			fm = g.getFontMetrics();
			int indexWidth = fm.stringWidth(i+"");
			g.drawString(i+"", (2*xpos + cellWidth - indexWidth)/2, ypos+3*cellHeight);			
			xpos += (cellWidth + cellGap);
		}
	}
	/*
	 * The following computes the height of the display area needed by the current
	 * heap, and if it won't fit in the scrolled panel, it enlarges the scrolled panel.
	 */
	void checkScrolledPanelSize() {
		// Compute space needed for heap array, then for trees.
		int heapHeightNeeded = 150;
		int heapWidthNeeded = (theHeap.n + 1) * cellWidth +
			(theHeap.n * cellGap) + leftMargin + rightMargin;
		Dimension d = visPanel.getPreferredSize();
		int currentHeight = (int) d.getHeight();
		int currentWidth = (int) d.getWidth();
		// Compute width needed for trees:
		int treesWidthNeeded = leftMargin + rightMargin;
		int treesHeightNeeded = 0;
		for (int i = 1; i <= theHeap.n; i++) {
			HuffmanTree ht = (HuffmanTree)theHeap.B[i];
			if (ht != null) {
				treesWidthNeeded += ht.getDisplayWidth();
				treesHeightNeeded = Math.max(treesHeightNeeded, ht.getDisplayHeight()); 
				}
		}
		treesWidthNeeded += Math.max(0, (theHeap.n - 1)*interTreeGap);
		treesHeightNeeded += topMargin + bottomMargin;
		// Enlarge scrolled panel if necessary:
		int widthNeeded = Math.max(heapWidthNeeded, treesWidthNeeded);
		int heightNeeded = heapHeightNeeded + treesHeightNeeded;
		if ((heightNeeded  > currentHeight) ||
			(widthNeeded > currentWidth)) { 
				visPanel.setPreferredSize(new Dimension(
					Math.max(currentWidth,widthNeeded), 
					Math.max(currentHeight,heightNeeded)));
			visPanel.revalidate(); // Adjust the vertical scroll bar.
		}
	}

	/*
	 * In order to keep the code simple, this one class
	 * is used for both leaf nodes and internal nodes.
	 * It's also used for the trees as a whole.
	 * If storage space were a big consideration, then
	 * specific classes could be created for each of these.
	 * However, with Huffman trees, space is not usually
	 * much of a concern.
	 */
	class HuffmanTree {
		
		// Here are the essential data members for Huffman trees:		
		int nSymbols; // number of symbols in the tree
		int weight; // used in all nodes.
		HuffmanTree leftSubtree, rightSubtree; // used only in internal nodes.
		String symbol; // used only in leaf nodes.

		// The following are used in display layout:		
		int depth; // relative y position of this node in the display
		int column; // relative x position in display, in units of nodes
		int maxdepth; // height of tree whose root is this node.
		int xcenter; // position of center of root relative to left side of tree.
		int ycenter;
		
		// Leaf node constructor:
		HuffmanTree(String sym, int weight) {
			symbol = sym;
			this.weight = weight;
			nSymbols = 1;
		}

		// Internal node constructor:
		HuffmanTree(HuffmanTree T0, HuffmanTree T1) {
			leftSubtree = T0;
			rightSubtree = T1;
			weight = T0.weight + T1.weight;
			nSymbols = T0.nSymbols + T1.nSymbols;
		}

		boolean isLeaf() { return (symbol != null); }
		String leftmostSymbol() {
			if (isLeaf()) { return symbol; }
			else { return leftSubtree.leftmostSymbol();}
		}
		void getCodes(Hashtable ht, String prefix) {
			if (isLeaf()) {
				ht.put(symbol, prefix);
			}
			else {
				leftSubtree.getCodes(ht, prefix + "0");
				rightSubtree.getCodes(ht, prefix + "1");
			}		
		}
		public String toString() { return leftmostSymbol(); }
		void paint(Graphics g, int xpos, int ypos) {
			treeColumns();
			paintHelper(g, xpos, ypos);
		}
		void paintHelper(Graphics g, int xpos, int ypos) {
			if (! (isLeaf())) {
				g.setColor(Color.black);
				g.drawLine(xcenter + xpos, ycenter + ypos,
							leftSubtree.xcenter + xpos,
							leftSubtree.ycenter + ypos);
				g.drawLine(xcenter + xpos, ycenter + ypos,
							rightSubtree.xcenter + xpos,
							rightSubtree.ycenter + ypos);								
				}
			g.setColor(Color.pink);
			g.fillRect(xpos + column*nodeHorizSpacing,
					   ypos + depth*nodeVertSpacing, 
					   nodeWidth, 
					   nodeHeight);
			g.setColor(Color.black);
			if (isLeaf()) {
				String adjustedSymbol = symbol;
				if (symbol.equals("\n")) {
					adjustedSymbol = "\\n";
				}
				g.drawString(adjustedSymbol + "," + weight, 
							 xpos + column*nodeHorizSpacing + leftOffset,
							 ypos + depth*nodeVertSpacing + nodeHeight - ycentering);			
			}
			else {
				g.drawString(weight + "", xpos + column*nodeHorizSpacing + leftOffset,
									 ypos + depth*nodeVertSpacing + nodeHeight - ycentering);			
				leftSubtree.paintHelper(g, xpos, ypos);
				rightSubtree.paintHelper(g, xpos, ypos);
			}
		}
		int traverse(int currentDepth) {
			// Traverse the tree, filling in the depth and the column index
			// of each node for display purposes.
			// Returns the column index of the rightmost node.
			depth = currentDepth;
			if (isLeaf()) { 
				column = m; 
				m++;
				maxdepth = depth; 
			}
			else {
				leftSubtree.traverse(depth + 1);
				column = m; m++;
			}
			xcenter = column*nodeHorizSpacing + (nodeWidth / 2);
			ycenter = depth*nodeVertSpacing + nodeHeight - ycentering;
			if (! isLeaf()) {
				int rm = rightSubtree.traverse(depth + 1);
				maxdepth = Math.max(leftSubtree.maxdepth,
									rightSubtree.maxdepth);	
				return rm;					
			}
			else {
				return column;
			}
		}
		int treeColumns() {
			m = 0;
			return traverse(0) + 1;
		}
		int getDisplayWidth() {
			int hGap = nodeHorizSpacing - nodeWidth;
			int val = treeColumns() * (nodeHorizSpacing) - hGap;
			return val;
		}
		int getDisplayHeight() {
			int val = (maxdepth + 1) * nodeVertSpacing - nodeVGap;
			return val;
		}
	}
	void frequenciesToForest(FreqTable ft) {
		theHeap = new HuffmanHeap(ft.n);
		theHeap.A[0] = -1;
		for (int i = 0; i < ft.n; i++) {
			theHeap.A[i+1] = ft.f[i];
			String s = (ft.c[i]+"");
			theHeap.B[i+1] = new HuffmanTree(new String(s), ft.f[i]);	
		}
		report("The forest of Huffman trees has been created.");
	}
	void mergeAll() {
		while(theHeap.n > 1) {
			HuffmanTree tMin = (HuffmanTree) theHeap.findminObject();
			theHeap.deletemin();
			HuffmanTree tMin2 = (HuffmanTree) theHeap.findminObject();
			theHeap.deletemin();
			HuffmanTree newTree = new HuffmanTree(tMin, tMin2);
			theHeap.insert(newTree, newTree.weight);
			checkScrolledPanelSize();
			updateDisplay();
		}
	}
	/*
	 * A FreqTable (frequence table) object consists primarly
	 * of two arrays: one to hold characters and one to hold
	 * int values that represent the numbers of occurrences of
	 * the corresponding characters.
	 */
	class FreqTable {
		char c[];
		int f[];
		int n;
		int maxSize;
		FreqTable(int maxSize) {
			c = new char[maxSize];
			f = new int[maxSize];
			n = 0;
			this.maxSize = maxSize;			
		}
		void insert(char c, int freq) {
			if (n == maxSize - 1) {
				report("Frequency table is full");
				return;
			}
			this.c[n] = c;
			f[n] = freq;
			n++;
		}
		/*
		 * Takes a text string and analyzes it, finding for
		 * each character, the number of its occurrences.
		 * Puts the characters and counts into the FreqTable.
		 */
		void fillTable(String text) {
			Hashtable ht = new Hashtable(40);
			for(int i = 0; i < text.length(); i++) {
				String keyStr = text.substring(i,i+1);
				Integer ii = (Integer) ht.get(keyStr);
				if (ii == null) {
					ht.put(keyStr, new Integer(1));
				}
				else { 
					int oldfreq = ii.intValue();
					ht.put(keyStr, new Integer(oldfreq + 1));
				}
			}
			for(Enumeration e = ht.keys(); e.hasMoreElements();) {
				String keyStr = (String)e.nextElement();
				insert(keyStr.charAt(0), ((Integer)(ht.get(keyStr))).intValue());
			}
		}
		/*
		 * Add a display of the frequency table to the history window.
		 */
		void show() {
			report("The frequency table:");
			for (int i = 0; i < n; i++) {
				String adjustedSymbol = c[i] + "";
				if (adjustedSymbol.equals("\n")) {
					adjustedSymbol = "\\n";
				}
				if (adjustedSymbol.equals(" ")) {
					adjustedSymbol = "(space)";
				}
				report(adjustedSymbol + ": " + f[i]);
			}
		}
	}
	
	/*
	 * A handy function that reports a message to both the
	 * status line of the applet and the history window.
	 * Multiline messages are not fully visible in the status line.
	 */
	void report(String message) {
		statusLine.setText(message);
		history.appendText("; " + message + "\n");
	}
	
	/*
	 * Goes through the heap from left to right, and each
	 * Huffman tree is painted underneath the heap, in a
	 * left-to-right sequence of trees.
	 */
	void paintTrees(Graphics g) {
		g.setFont(treeFont);
		int ystart = yTreeTops; 
		int ypos = ystart;
		int xpos = leftMargin;
		g.setFont(headingFont);
		g.setColor(Color.BLACK);
		g.drawString("The Huffman Trees:", xpos, yTreeTops - 50);
		for (int i = 1; i <= theHeap.n; i++) {
			HuffmanTree ht = (HuffmanTree)theHeap.B[i];
			if (ht != null) { 
				g.setColor(Color.BLUE);
				g.setFont(headingFont);
				g.drawString( "T" + i + ":", xpos, yTreeTops - fontSize - 4);
				g.setColor(Color.BLACK);
				g.setFont(treeFont);	
				ht.paint(g,xpos,ypos);
				xpos += interTreeGap;
				xpos += ht.getDisplayWidth();
			} 
		}
	}
	
	/*
	 * Displays a white grid, useful in debugging the tree layout
	 * part of the problem.
	 * The size of the grid is somewhat arbitrary.
	 */
	void paintGrid(Graphics g) {
		g.setColor(Color.white);
		for (int i = 0; i < 60; i ++) {
			g.drawLine(0, i*20, 1500, i*20);
			g.drawLine(i*20, 0, i*20, 1500);
		}
	}
	
	/*
	 * Represents a mapping from characters (represented as
	 * strings of length 1) to codewords (strings of 0s and 1s
	 * of possibly different lengths).
	 */
	class CodingTable extends Hashtable {
		int tableNBits; // number of bits to store table, in principle,
		// assuming 1-byte per char, two bits per code bit.
		// This is only useful in estimating compression ratios.
		
		CodingTable(HuffmanTree ht) {
			ht.getCodes(this, "");
		}
		int computeTableNBits() {
			tableNBits = 0;
			for (Enumeration e = this.keys(); 
					e.hasMoreElements();) {
				String key = (String)e.nextElement();
				String code = (String)get(key);
				tableNBits += 1 + 3*code.length();
			}	
			return tableNBits;		
		}
		void show() {
			report("The coding table:");
			for (Enumeration e = this.keys(); 
					e.hasMoreElements();) {
				String key = (String)e.nextElement();
				String code = (String)get(key);
				String adjustedKey = key;
				if (key.equals("\n")) {
					adjustedKey = "\\n";
				}
				if (key.equals(" ")) {
					adjustedKey = "(space)";
				}
				report(adjustedKey + ": " + code);
			}
		}
	}
	
	/*
	 * Similar to CodingTable, but mapping the other way.
	 */
	class DecodingTable extends Hashtable {
		void init(CodingTable ct) {
			for (Enumeration e = ct.keys(); 
					e.hasMoreElements();) {
				String key = (String)e.nextElement();
				String code = (String)ct.get(key);
				put(code, key);
			}
		}
		void show() {
			report("The decoding table:");
			for (Enumeration e = this.keys(); 
					e.hasMoreElements();) {
				String key = (String)e.nextElement();
				String sym = (String)get(key);
				String adjustedSym = sym;
				if (sym.equals("\n")) {
					adjustedSym = "\\n";
				}
				if (sym.equals(" ")) {
					adjustedSym = "(space)";
				}
				report(key + ": " + adjustedSym);
			}
		}
	}

}