import javax.swing.*;
import javax.swing.colorchooser.ColorSelectionModel;
import java.util.*;
import java.awt.event.*;
import java.awt.*;
import java.io.*;
import javax.swing.border.*;
import javax.swing.event.*;
import javax.swing.tree.*;

public class ConfigurationPanel extends JDialog implements myoPyx.Listener, Observer {
	private Properties _configuration;
	private static final File PROPS_FILE =
	    new File(System.getProperty("user.home"), ".YesClock");
	private JTextField _latitude, _longitude, _timezone;
	private Hashtable _contents = null;
	private JLabel _message;
	private Yes _callback;

	public ConfigurationPanel( JFrame c ) {
		super(c, "Configuration" );
		JTabbedPane contents = new JTabbedPane();
		contents.add( "Timezone", makeTimeZonePanel() );
		contents.add( "Colors", makeColorsPanel() );
		contents.add( "Display", makeDisplayPanel() );
		//contents.add( "Fonts", makeFontsPanel() );

		getContentPane().setLayout( new BorderLayout() );
		getContentPane().add( contents, BorderLayout.CENTER );
		getContentPane().add( makeSaveCancel(), BorderLayout.SOUTH );

		pack();
		setSize( 640, 600 );
	}

	private Box makeSaveCancel() {
		// ************************************
		// Here create the Save/Cancel buttons
		final JDialog stupid_java = this;
		// Add a "Save" button that calls saveConfiguration and closes this
		JButton save = new JButton( "Save" );
		save.addActionListener( new ActionListener() {
			                        public void actionPerformed( ActionEvent ae ) {
										// Get the values from the widgets, place in _configuration
										JTextField c=null;
										try {
											// Validate the widget values
											c = _latitude;
											Float.valueOf(c.getText());
											c = _longitude;
											Float.valueOf(c.getText());
											c = _timezone;
											Integer.valueOf(c.getText());
											clear();
											// Put the widget values into the configuration
											widgetsToConfig();
											saveConfiguration();
											_callback.updateConfiguration();
											stupid_java.setVisible( false );
										} catch (Exception e) {
											_message.setText("<HTML><H2><COLOR='#F00'>Bad number "+
												"format!</COLOR></H2></HTML>");
											c.setBackground(Color.red);
										}
									}
		});
		// Add a "Cancel" button that closes
		JButton cancel = new JButton( "Cancel" );
		cancel.addActionListener( new ActionListener() {
			                          public void actionPerformed( ActionEvent ae ) {
				                          // revert values;
																	configToWidgets();
																	clear();
				                          stupid_java.setVisible( false );
			                          }
		                          });

		Box buttonbox = Box.createHorizontalBox();
		buttonbox.add(save);
		buttonbox.add(cancel);
		return buttonbox;
	}

	private JPanel makeColorsPanel() {
		JPanel colors = new JPanel();
		final JColorChooser color_chooser = new JColorChooser();
		final YesClockWrapper clock = new YesClockWrapper( _configuration );
		color_chooser.setPreviewPanel( clock );
		color_chooser.getSelectionModel().addChangeListener( clock );
		colors.setLayout( new BorderLayout() );
		JPanel dn_panel = new JPanel();
		final MyColorBox day = new MyColorBox( "Day", _configuration );
		final MyColorBox night = new MyColorBox( "Night", _configuration );
		final MyColorBox background = new MyColorBox( "Background", _configuration );
		ActionListener listener = new ActionListener() {
			public void actionPerformed( ActionEvent ae ) {
				MyColorBox source = (MyColorBox)ae.getSource();
				color_chooser.getSelectionModel().removeChangeListener( day );
				color_chooser.getSelectionModel().removeChangeListener( night );
				color_chooser.getSelectionModel().removeChangeListener( background );
				color_chooser.getSelectionModel().addChangeListener( source );
				clock.setCurrent( source.getName() );
				// Always do the following LAST
				color_chooser.setColor( source.getColor() );
			}
		};
		day.addActionListener( listener );
		night.addActionListener( listener );
		background.addActionListener( listener );
		dn_panel.add( day );
		dn_panel.add( night );
		dn_panel.add( background );
		colors.add( color_chooser, BorderLayout.CENTER );
		colors.add( dn_panel, BorderLayout.SOUTH );
		return colors;
	}

	private JPanel makeDisplayPanel() {
		JPanel panel = new JPanel();
		boolean tf = true;
		// Show markers
		if (_configuration.getProperty("markers") != null &&
				_configuration.getProperty("markers").equals("false") )
			tf = false;
		else tf = true;
		JCheckBox markers = new JCheckBox( "Show Markers", tf );
		markers.addChangeListener( new ChangeListener() {
			public void stateChanged( ChangeEvent e ) {
				_configuration.setProperty( "markers", String.valueOf(((AbstractButton)e.getSource()).isSelected()) );
			}
		});
		// Show time
		if (_configuration.getProperty("time") != null &&
				_configuration.getProperty("time").equals("false") )
			tf = false;
		else tf = true;
		JCheckBox time = new JCheckBox( "Show Digital Time", tf );
		time.addChangeListener( new ChangeListener() {
			public void stateChanged( ChangeEvent e ) {
				_configuration.setProperty( "time", String.valueOf(((AbstractButton)e.getSource()).isSelected()) );
			}
		});
		// Show jewels
		if (_configuration.getProperty("jewels") != null &&
				_configuration.getProperty("jewels").equals("false") )
			tf = false;
		else tf = true;
		JCheckBox jewels = new JCheckBox( "Show Jewels", tf );
		jewels.addChangeListener( new ChangeListener() {
			public void stateChanged( ChangeEvent e ) {
				_configuration.setProperty( "jewels", String.valueOf(((AbstractButton)e.getSource()).isSelected()) );
			}
		});
		panel.setLayout( new BoxLayout( panel, BoxLayout.Y_AXIS ) );
		panel.add( markers );
		panel.add( time );
		panel.add( jewels );
		return panel;
	}
	
	private JPanel makeFontsPanel() {
		JPanel fonts = new JPanel();
		return fonts;
	}

	private JPanel makeTimeZonePanel() {
		// Create properties file, load if exists
		_configuration = getConfigurationProps();
		loadCities();

		// **********************************
		// Here create the location selection
		// Add some widgets here...
		JTree tree = new JTree(_contents);
		tree.addTreeSelectionListener( new TreeSelectionListener() {
			public void valueChanged( TreeSelectionEvent e ) {
				TreePath path = e.getNewLeadSelectionPath();
				if (path == null) return;
				Object uobject = ((DefaultMutableTreeNode)path.
					getLastPathComponent()).getUserObject();
				if (uobject instanceof City ) {
					City city = (City)uobject;
					_longitude.setText( city.longitude );
					_longitude.setBackground(Color.white);
					_latitude.setText( city.latitude );
					_latitude.setBackground(Color.white);
					_timezone.setText( city.timezone );
					_timezone.setBackground(Color.white);
				}
			}
		});

		JLabel instructions =
		    new JLabel("<html>Select the city closest to you from the "+
						"list to the left, or click on your location on the "+
						"map below, or set your Longitude and Latitude by "+
						"entering the values in the text areas below.  "+
						"Note that using the map below will not set your timezone. "+
						"Check the timezone; it is set by default from your operating "+ 
						"system, but it may be wrong."+
						"</html>" );
		MapPanel map = new MapPanel();
		map.addObserver(this);
		_message = new JLabel("");
		_longitude = new JTextField();
		JPanel p1 = new JPanel();
		p1.setLayout( new BorderLayout() );
		p1.setBorder( new TitledBorder(new EtchedBorder(), "Longitude"));
		p1.add( _longitude, BorderLayout.CENTER );

		_latitude = new JTextField();
		JPanel p2 = new JPanel();
		p2.setLayout( new BorderLayout() );
		p2.setBorder( new TitledBorder( new EtchedBorder(), "Latitude" ));
		p2.add( _latitude, BorderLayout.CENTER );

		_timezone = new JTextField();
		JPanel p3 = new JPanel();
		p3.setLayout( new BorderLayout() );
		p3.setBorder( new TitledBorder( new EtchedBorder(), "Timezone" ));
		p3.add( _timezone, BorderLayout.CENTER );


		Box rbot = Box.createVerticalBox();
		rbot.add(map);
		rbot.add(_message);
		rbot.add(p1);
		rbot.add(p2);
		rbot.add(p3);
		
		JPanel right = new JPanel();
		right.setLayout( new BorderLayout() );
		right.add( instructions, BorderLayout.CENTER );
		right.add( rbot, BorderLayout.SOUTH );
		
		JSplitPane locationbox = new JSplitPane( JSplitPane.HORIZONTAL_SPLIT );
		locationbox.setLeftComponent(new JScrollPane(tree));
		locationbox.setRightComponent(right);
		locationbox.setResizeWeight( 0.50 );

		JPanel tz_panel = new JPanel();
		tz_panel.setLayout( new BorderLayout() );
		tz_panel.add( locationbox, BorderLayout.CENTER );
		//tz_panel.add( buttonbox, BorderLayout.SOUTH );
		return tz_panel;
	}

	private void clear() {
		_message.setText("");
		_latitude.setBackground(Color.white);
		_longitude.setBackground(Color.white);
		_timezone.setBackground(Color.white);
	}

	private void widgetsToConfig() {
		_configuration.setProperty("latitude", _latitude.getText() );
		_configuration.setProperty("longitude", _longitude.getText() );
		_configuration.setProperty("timezone", _timezone.getText() );
	}

	private void configToWidgets() {
		_latitude.setText( _configuration.getProperty("latitude") );
		_longitude.setText( _configuration.getProperty("longitude") );
		_timezone.setText( _configuration.getProperty("timezone") );
	}

	public static Properties getConfigurationProps(){
		try {
			Properties configuration = new Properties();
			if (PROPS_FILE.exists()) {
				InputStream in = new FileInputStream( PROPS_FILE );
				configuration.load(in);
				in.close();
			}
			return configuration;
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return null;
	}

	public Properties getConfiguration() { return _configuration; }
	
	public void expose( Yes y ) {
		_callback = y;
		configToWidgets();
		setVisible( true );
	}

	/** Save the configuration to the config file.  We have to be clever about
	 * this, though, because this method may be called quite a bit, and we only
	 * really need to save the last one;  another way to do this would be to
	 * only save the configuration when the application exits, but that would
	 * mean that the configuration wouldn't be saved in the case of a crash
	 * or unfriendly exit.  Therefore, when the method is called, we fire off
	 * a thread that waits until the save events have stopped coming in for
	 * a reasonable amount of time (say 5 seconds), and then saves the
	 * configuration.
	 */
	private Thread _save_thread;
	private boolean _buffer;
	public void saveConfiguration() {
		_buffer = true;
		if (_save_thread == null || !_save_thread.isAlive()) {
			_save_thread = new Thread() {
				public void run() {
					while (_buffer) {
						try {
							_buffer = false;
							Thread.sleep(5000);
						} catch (Exception e) {
						}
					}
					try {
						OutputStream out = new FileOutputStream(PROPS_FILE);
						_configuration.store( out, "YES Clock configuration file" );
						out.close();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}; 
			_save_thread.start();
		}
	}

	public void update( Observable o, Object arg ) {
		_latitude.setText( String.valueOf(((MapPanel.Location)arg).lat) );
		_longitude.setText( String.valueOf(((MapPanel.Location)arg).lon) );
	}
	
	public static final void main( String[] args ) {
		JFrame frame = new JFrame( "Test" );
		ConfigurationPanel p = new ConfigurationPanel( frame );
		p.pack();
		p.setVisible( true );
	}
	private void loadCities() {
		myoPyx p = new myoPyx();
		p.addListener( null, this );
		Reader r = new InputStreamReader(ClassLoader.getSystemResourceAsStream( "cities.pyx" ));
		try {
			p.parse(r);
		} catch( Exception e ) {
			e.printStackTrace();
		}
	}
	
	private String _continent, _region;
	public void startElement( String name, HashMap attributes ) {
		if (name.equals("Continent") ) {
			_continent = (String)attributes.get("name");
			_contents.put( _continent, new Hashtable() );
		} else if (name.equals("Region")) {
			_region = (String)attributes.get("name");
			((Hashtable)_contents.get(_continent)).put( _region, new Hashtable() );
		} else if (name.equals("City")) {
			Hashtable h = (Hashtable)_contents.get(_continent);
			if (_region != null) h = (Hashtable)h.get(_region);
			h.put( new City( attributes ), "" );
		} else if (name.equals("Cities")) {
			_contents = new Hashtable();
		}
	}
	public void endElement( String name ) {
		if (name.equals("Region")) _region = null;
		if (name.equals("Continent")) _continent = null;
	}
	public void text( String text ) { }

	private class City {
		public String name, latitude, longitude, timezone;
		public City( HashMap props ) {
			name = (String)props.get("name");
			latitude = (String)props.get("lat");
			longitude = (String)props.get("lon");
			timezone = (String)props.get("tz");
		}
		public String toString() {
			return name;
		}
	}
}

class MyColorBox extends JButton implements ChangeListener {
	private Properties _configuration;
	private String _name;
	public MyColorBox( String name, Properties config ) {
		super( name );
		_configuration = config;
		_name = name.toLowerCase();

		String color = _name.equals("night") ? 
			"#595959" : 
			_name.equals("background") ? 
				"#0000FF" :
				"#9696FF" ;
		Color c = Color.decode( _configuration.getProperty(_name, color) );
		setColor( c );
	}

	public String getName() { return _name; }
	public Color getColor() { return getBackground(); }
	public void setColor( Color c ) { 
		setBackground(c);
		StringBuffer color = new StringBuffer("#");
		color.append( makeHex(c.getRed()) );
		color.append( makeHex( c.getGreen() ) );
		color.append( makeHex( c.getBlue() ) );
		_configuration.setProperty( _name, color.toString() );
	}
	private String makeHex( int c ) {
		String s = Integer.toHexString( c );
		if(s.length()==1) s = "0"+s;
		return s;
	}
	public void stateChanged( ChangeEvent e ) {
		Color c = ((ColorSelectionModel)e.getSource()).getSelectedColor();
		setColor(c);
	}
}

class YesClockWrapper extends YesClock implements ChangeListener {
	private String _current;
	public YesClockWrapper( Properties config ) {
		Dimension d = new Dimension(100,100);
		setMinimumSize( d );
		setPreferredSize( d );
		if (config.getProperty("day")!=null) 
			setDay(Color.decode(config.getProperty("day")));
		if (config.getProperty("night")!=null) 
			setNight(Color.decode(config.getProperty("night")));
		if (config.getProperty("background")!=null) 
			setBackground(Color.decode(config.getProperty("background")));
	}
	public void stateChanged( ChangeEvent e ) {
		if (_current == null) return;
		Color c = ((ColorSelectionModel)e.getSource()).getSelectedColor();
		if (_current.equals( "day" ) ) {
			setDay( c );
		} else if (_current.equals( "night" ) ) {
			setNight( c );
		} else {
			setBackground( c );
		}
		redraw();
		repaint();
	}
	public void setCurrent( String name ) {
		_current = name;
	}
}
