package com.hero.ui.widgets;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.Document;

import com.hero.model.FractionDocument;
import com.hero.model.MultiplierDocument;
import com.hero.util.Rounder;

/**
 * Copyright (c) 2000 - 2005, CompNet Design, Inc. All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, is prohibited unless the following conditions are met: 1.
 * Express written consent of CompNet Design, Inc. is obtained by the developer.
 * 2. Redistributions must retain this copyright notice. THIS SOFTWARE IS
 * PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * @author CompNet Design, Inc.
 * @version $Revision$
 */

public class FractionTF extends JComponent {

	private static final long serialVersionUID = -3419149473699452202L;

	double current;

	double max;

	double min;

	JTextField tf;

	JButton inc;

	JButton dec;

	DocumentListener tfListener;

	boolean incMouseDown;

	boolean decMouseDown;

	boolean useMultiplier = false;

	double oldVal = -9999999;

	public FractionTF(double start, double max, double min) {
		super();
		setLayout(new BorderLayout());
		current = start;
		this.max = max;
		this.min = min;
		if (max < min) {
			max = min;
		}
		if (current < min) {
			current = min;
		}
		if (current > max) {
			current = max;
		}
		initWidgets();
		initListeners();
		layoutComponent();
		setCurrent(current);
	}

	public FractionTF(double start, double max, double min,
			boolean useMultiplier) {
		super();
		setLayout(new BorderLayout());
		this.useMultiplier = useMultiplier;
		current = start;
		this.max = max;
		this.min = min;
		if (max < min) {
			max = min;
		}
		if (current < min) {
			current = min;
		}
		if (current > max) {
			current = max;
		}
		initWidgets();
		initListeners();
		layoutComponent();
		setCurrent(current);
	}

	void checkTF() {
		String check = tf.getText();
		if (check.length() == 0) {
			return;
		}
		try {
			int mult = 1;
			int number = 0;
			int numerator = 0;
			int denomenator = 0;
			if (!useMultiplier) {
				if (check.startsWith("+")) {
					check = check.substring(1, check.length());
				} else if (check.startsWith("-")) {
					check = check.substring(1, check.length());
					mult = -1;
				}
				if (check.length() == 0) {
					setForeground(Color.red);
					return;
				}
				if (check.indexOf(" ") > 0) {
					number = Integer.parseInt(check.substring(0, check
							.indexOf(" ")));
					check = check.substring(check.indexOf(" ") + 1, check
							.length());
					if (check.indexOf("/") > 0) {
						numerator = Integer.parseInt(check.substring(0, check
								.indexOf("/")));
						if (check.indexOf("/") < check.length() - 1) {
							denomenator = Integer.parseInt(check.substring(
									check.indexOf("/") + 1, check.length()));
						}
					}
				} else if (check.indexOf("/") > 0) {
					numerator = Integer.parseInt(check.substring(0, check
							.indexOf("/")));
					if (check.indexOf("/") < check.length() - 1) {
						denomenator = Integer.parseInt(check.substring(check
								.indexOf("/") + 1, check.length()));
					}
				} else {
					number = Integer.parseInt(check);
				}
				number = number * mult;
				if ((number < min) || (number > max)) {
					tf.setForeground(Color.red);
				} else if ((numerator > 0) && (denomenator == 0)) {
					tf.setForeground(Color.red);
				} else {
					tf.setForeground(Color.black);
					double num = number;
					if (denomenator != 0) {
						num = num * mult;
						num += (double) numerator / (double) denomenator;
						num = num * mult;
					}
					setCurrent(num);
				}
			} else {
				if (check.startsWith("x")) {
					check = check.substring(1, check.length());
				}
				if (check.startsWith("-")) {
					check = check.substring(1, check.length());
					mult = -1;
				}
				if (check.length() == 0) {
					setForeground(Color.red);
					return;
				}
				if (check.indexOf(" ") > 0) {
					number = Integer.parseInt(check.substring(0, check
							.indexOf(" ")));
					check = check.substring(check.indexOf(" ") + 1, check
							.length());
					if (check.indexOf("/") > 0) {
						numerator = Integer.parseInt(check.substring(0, check
								.indexOf("/")));
						if (check.indexOf("/") < check.length() - 1) {
							denomenator = Integer.parseInt(check.substring(
									check.indexOf("/") + 1, check.length()));
						}
					}
				} else if (check.indexOf("/") > 0) {
					numerator = Integer.parseInt(check.substring(0, check
							.indexOf("/")));
					if (check.indexOf("/") < check.length() - 1) {
						denomenator = Integer.parseInt(check.substring(check
								.indexOf("/") + 1, check.length()));
					}
				} else {
					number = Integer.parseInt(check);
				}
				number = number * mult;
				if ((numerator > 0) && (denomenator == 0)) {
					tf.setForeground(Color.red);
				} else {
					tf.setForeground(Color.black);
					double num = number;
					if (denomenator != 0) {
						num = num * mult;
						num += (double) numerator / (double) denomenator;
						num = num * mult;
						num = num - 1;
					}
					if ((num < min) || (num > max)) {
						tf.setForeground(Color.red);
					}
					setCurrent(num);
				}

			}
		} catch (Exception e) {
			setCurrent(current);
			e.printStackTrace();
		}
	}

	/**
	 * Returns the current value of the field.
	 * 
	 * @return
	 */
	public double getCurrent() {
		return current;
	}

	/**
	 * Returns the Document that is used by the underlying JTextField.
	 * 
	 * @return
	 */
	public Document getDocument() {
		return tf.getDocument();
	}

	public String getFraction(double val) {
		return getFraction(val, useMultiplier);
	}

	private String getFraction(double val, boolean mult) {
		String ret = "";
		if (val == 0) {
			if (mult) {
				return "x1";
			} else {
				return "+0";
			}
		}
		if (mult) {
			ret = "x";
			boolean isNeg = false;
			if (val < 0) {
				isNeg = true;
				val = Math.abs(val);
			}
			String frac = "";
			long check = Rounder.roundDown(val + .0000001);
			if (check < val) {
				frac = getFraction(val - check, false);
				if (frac.startsWith("+")) {
					frac = frac.substring(1, frac.length());
				}
			}
			if (frac.trim().length() > 0) {
				frac = " " + frac;
			}
			check++;
			if (isNeg) {
				// if (frac.length() > 0)
				// ret += "(";
				ret += "1/" + check + frac;
				// if (frac.length() > 0)
				// ret += ")";

			} else {
				// if (frac.length() > 0)
				// ret += "(";
				ret += check + frac;
				// if (frac.length() > 0)
				// ret += ")";
			}
			return ret;
		}

		if (val < 0) {
			ret += "-";
		} else {
			ret += "+";
		}
		val = Math.abs(val);
		if (val > 1) {
			if (mult) {
				ret += (int) Rounder.roundDown(val + 1);
			} else {
				ret += (int) Rounder.roundDown(val);
			}
			val = val - Rounder.roundDown(val);
		}
		if (val == 0) {
			return ret;
		}
		String closestMatch = "";
		double closest = 1d;
		if (Math.abs((double) 1 / (double) 4 - val) < closest) {
			closest = Math.abs((double) 1 / (double) 4 - val);
			closestMatch = "1/4";
		}
		if (Math.abs((double) 1 / (double) 2 - val) < closest) {
			closest = Math.abs((double) 1 / (double) 2 - val);
			closestMatch = "1/2";
		}
		if (Math.abs((double) 3 / (double) 4 - val) < closest) {
			closest = Math.abs((double) 3 / (double) 4 - val);
			closestMatch = "3/4";
		}
		if (Math.abs(1 - val) < closest) {
			closestMatch = "";
			if (ret.length() > 1) {
				int check = Integer.parseInt(ret.substring(1, ret.length()));
				ret = ret.substring(0, 1) + (check + 1);
			} else if ((ret.length() == 1) && !ret.equals("+")
					&& !ret.equals("-") && !ret.equals("x")) {
				ret = "" + (Integer.parseInt(ret) + 1);
			} else if ((ret.trim().length() == 0) || ret.trim().equals("+")) {
				ret = "+1";
			} else {
				ret = "-1";
			}
		}
		if (ret.length() > 1) {
			ret += " ";
		}
		ret += closestMatch;
		ret = ret.trim();
		return ret;
	}

	private void initListeners() {
		inc.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				double check = current + .25;
				if (check <= max) {
					setCurrent(check);
				}
			}
		});
		inc.addMouseListener(new MouseAdapter() {
			Thread runner;

			double check;

			@Override
			public void mousePressed(MouseEvent e) {
				incMouseDown = true;
				if ((runner == null) || !runner.isAlive()) {
					runner = new Thread() {
						@Override
						public void run() {
							int count = 0;
							while (incMouseDown) {
								try {
									Thread.sleep(count == 0 ? 1000
											: count > 10 ? 200
													: count > 50 ? 100 : 500);
									count++;
									if (!incMouseDown) {
										return;
									}
									check = current + .25;
									if (check <= max) {
										SwingUtilities
												.invokeAndWait(new Runnable() {
													public void run() {
														setCurrent(check);
													}
												});
									} else {
										return;
									}
								} catch (Exception exp) {
									return;
								}
							}
						}
					};
					runner.start();
				}
			}

			@Override
			public void mouseReleased(MouseEvent e) {
				incMouseDown = false;
			}
		});
		inc.addFocusListener(new FocusListener() {
			public void focusGained(FocusEvent e) {

			}

			public void focusLost(FocusEvent e) {
				incMouseDown = false;
			}
		});
		dec.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				double check = current - .25;
				if (check >= min) {
					setCurrent(check);
				}
			}
		});
		dec.addMouseListener(new MouseAdapter() {
			Thread runner;

			double check;

			@Override
			public void mousePressed(MouseEvent e) {
				decMouseDown = true;
				if ((runner == null) || !runner.isAlive()) {
					runner = new Thread() {
						@Override
						public void run() {
							int count = 0;
							while (decMouseDown) {
								try {
									Thread.sleep(count == 0 ? 1000
											: count > 10 ? 200
													: count > 50 ? 100 : 500);
									count++;
									if (!decMouseDown) {
										return;
									}
									check = current - .25;
									if (check >= min) {
										SwingUtilities
												.invokeAndWait(new Runnable() {
													public void run() {
														setCurrent(check);
													}
												});
									} else {
										return;
									}
								} catch (Exception exp) {
									return;
								}
							}
						}
					};
					runner.start();
				}
			}

			@Override
			public void mouseReleased(MouseEvent e) {
				decMouseDown = false;
			}
		});
		dec.addFocusListener(new FocusListener() {
			public void focusGained(FocusEvent e) {

			}

			public void focusLost(FocusEvent e) {
				decMouseDown = false;
			}
		});
		tfListener = new DocumentListener() {
			public void changedUpdate(DocumentEvent e) {
				checkTF();
			}

			public void insertUpdate(DocumentEvent e) {
				checkTF();
			}

			public void removeUpdate(DocumentEvent e) {
				checkTF();
			}
		};
		tf.getDocument().addDocumentListener(tfListener);
		tf.addFocusListener(new FocusListener() {
			public void focusGained(FocusEvent e) {
				setCurrent(current);
			}

			public void focusLost(FocusEvent e) {
				setCurrent(current);
			}
		});
	}

	private void initWidgets() {
		if (useMultiplier) {
			tf = new JTextField(new MultiplierDocument(max, min),
					getFraction(current), 5);
		} else {
			tf = new JTextField(new FractionDocument(max, min),
					getFraction(current), 5);
		}
		tf.setHorizontalAlignment(SwingConstants.CENTER);
		inc = new JButton(">");
		inc.setMargin(new Insets(0, 0, 0, 0));
		dec = new JButton("<");
		dec.setMargin(new Insets(0, 0, 0, 0));
	}

	private void layoutComponent() {
		add(dec, BorderLayout.WEST);
		add(tf, BorderLayout.CENTER);
		add(inc, BorderLayout.EAST);
	}

	/**
	 * Sets the current value of the field.
	 * 
	 * @param val
	 */
	public void setCurrent(double val) {
		tf.getDocument().removeDocumentListener(tfListener);
		try {
			tf.setText(getFraction(val));
		} catch (Exception ex) {
		}
		current = val;
		if (getCurrent() >= max) {
			inc.setEnabled(false);
		} else {
			inc.setEnabled(true);
		}
		if (getCurrent() <= min) {
			dec.setEnabled(false);
		} else {
			dec.setEnabled(true);
		}
		if (getCurrent() != oldVal) {
			double temp = oldVal;
			oldVal = getCurrent();
			firePropertyChange("Level", temp, getCurrent());
		}
		tf.getDocument().addDocumentListener(tfListener);
	}

	/**
	 * Sets whether this widget is enabled or not.
	 */
	@Override
	public void setEnabled(boolean enabled) {
		tf.setEnabled(enabled);
		inc.setEnabled(enabled);
		dec.setEnabled(enabled);
		if (enabled) {
			setCurrent(getCurrent());
		}
	}
}