/*******************************************************************************
 * This file is part of GECAMed.
 * 
 * GECAMed is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License (L-GPL) as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * GECAMed is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License (L-GPL)
 * along with GECAMed.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * GECAMed is Copyrighted by the Centre de Recherche Public Henri Tudor (http://www.tudor.lu)
 * (c) CRP Henri Tudor, Luxembourg, 2008
 *******************************************************************************/
package lu.tudor.santec.gecamed.address.gui.widgets;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.text.JTextComponent;

import lu.tudor.santec.gecamed.address.ejb.entity.beans.Country;
import lu.tudor.santec.gecamed.address.ejb.entity.beans.Locality;
import lu.tudor.santec.gecamed.address.ejb.entity.beans.Zip;
import lu.tudor.santec.gecamed.address.ejb.session.beans.AddressManagerBean;
import lu.tudor.santec.gecamed.address.ejb.session.interfaces.AddressManagerInterface;
import lu.tudor.santec.gecamed.core.gui.GECAMedFonts;
import lu.tudor.santec.gecamed.core.gui.GECAMedLists;
import lu.tudor.santec.gecamed.core.gui.GECAMedModule;
import lu.tudor.santec.gecamed.core.gui.IconFetcher;
import lu.tudor.santec.gecamed.core.gui.MainFrame;
import lu.tudor.santec.gecamed.core.gui.utils.AutoCompletion;
import lu.tudor.santec.gecamed.core.gui.utils.DefaultComboBoxRenderer;
import lu.tudor.santec.gecamed.core.gui.utils.GECAMedGuiUtils;
import lu.tudor.santec.gecamed.core.gui.widgets.LimitTextField;
import lu.tudor.santec.gecamed.core.gui.widgets.NamedComponent;
import lu.tudor.santec.gecamed.core.utils.ManagerFactory;
import lu.tudor.santec.gecamed.formeditor.gui.model.ComboBoxElement;
import lu.tudor.santec.gecamed.patient.gui.simpleview.OverviewPanel;
import lu.tudor.santec.i18n.Translatrix;
import lu.tudor.santec.widgets.gui.ButtonFactory;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;

/**
 * JPanel to enter and edit addresses with zip/locality/street+nr/country. If
 * the selected country is Luxembourg, location and street will be automatically
 * completed when zipcode is entered
 * 
 * @author Johannes Hermen johannes.hermen(at)tudor.lu
 * 
 * @Version <br>
 *          $Log: AddressPanel.java,v $ Revision 1.24 2013-12-27 18:09:25 donak
 *          Cleanup of imports
 *
 *          Revision 1.23 2013-07-15 06:18:36 ferring logging changed
 *
 *          Revision 1.22 2013-04-23 09:35:38 troth Bug Fix: Zip code get lost
 *          if all zip codes was fetch by the function 'fetchAllZipcodes'. <br>
 *          Revision 1.21 2013-04-04 15:13:02 troth <br>
 *          1. Order the zips <br>
 *          2. Clear zips from double entries <br>
 *          3. Bug: country names are not correct displayed and add several
 *          times to the country box. Fix: Load country translation before
 *          country box is fill up. <br>
 *          <br>
 *          Revision 1.20 2013-03-27 16:50:52 troth <br>
 *          Add to close the street and zip popups if a other country is
 *          selected as luxembourg. <br>
 *          <br>
 *          Revision 1.19 2013-03-26 11:20:47 troth <br>
 *          Fix Ticket #806. <br>
 *          <br>
 *          Revision 1.18 2013-02-19 14:36:01 ferring <br>
 *          import removed <br>
 *          <br>
 *          Revision 1.17 2013-02-19 12:07:34 ferring <br>
 *          GECAMedLists changed. Will now automatically load list of all beans
 *          <br>
 *          <br>
 *          Revision 1.16 2012-02-29 10:30:35 ferring <br>
 *          fixed loading of translations <br>
 *          <br>
 *          Revision 1.15 2012-02-29 09:39:51 ferring <br>
 *          fixed loading of translations <br>
 *          <br>
 *          Revision 1.14 2010-10-19 14:37:14 troth <br>
 *          rename class SimpleViewPanel in OverviewPanel <br>
 *          <br>
 *          Revision 1.13 2010-07-05 12:16:45 hermen <br>
 *          *** empty log message *** <br>
 *          <br>
 *          Revision 1.12 2010-03-17 11:28:55 hermen <br>
 *          updated <br>
 *          <br>
 *          Revision 1.11 2009-08-03 08:40:01 mack <br>
 *          Fixed a bug causing an NPE when an unkown locality was entered <br>
 *          <br>
 *          Revision 1.10 2009-07-13 10:18:09 hermen <br>
 *          fixed popup not hiding problem <br>
 *          <br>
 *          Revision 1.9 2009-07-02 11:50:43 hermen <br>
 *          fixed address/zipcode bug <br>
 *          <br>
 *          Revision 1.8 2009-03-10 09:51:42 hermen <br>
 *          small bugfixes <br>
 *          <br>
 *          Revision 1.7 2009-02-12 13:05:46 hermen <br>
 *          fixed nullpointer <br>
 *          <br>
 *          Revision 1.6 2009-01-14 14:30:43 hermen <br>
 *          added opening of maps to show address <br>
 *          <br>
 *          Revision 1.5 2009-01-13 09:15:42 hermen <br>
 *          reload addresses/zips after import bug id 209 <br>
 *          <br>
 *          Revision 1.4 2008-09-25 09:42:27 heinemann <br>
 *          fixed copyrights <br>
 *          <br>
 *          Revision 1.3 2008-08-19 10:25:08 heinemann <br>
 *          cleanup <br>
 *          <br>
 *          Revision 1.2 2008-08-11 09:17:05 hermen <br>
 *          fixed saving of addresses <br>
 *          <br>
 *          Revision 1.1 2008-06-30 08:16:45 hermen <br>
 *          added address autocompletion by locality <br>
 *          <br>
 *          Revision 1.33 2008-05-09 13:22:58 heinemann <br>
 *          *** empty log message *** <br>
 *          <br>
 *          Revision 1.32 2008-04-16 14:24:28 heinemann <br>
 *          *** empty log message *** <br>
 *          <br>
 *          Revision 1.31 2008-01-15 09:29:38 hermen <br>
 *          updated Javadoc and refactured code <br>
 *          <br>
 *          Revision 1.30 2007-12-06 14:46:44 hermen <br>
 *          updated Javadoc and refactured code <br>
 * 
 */
@SuppressWarnings({ "unchecked", "rawtypes" })
public class AddressPanel extends JPanel implements KeyListener, ItemListener {
	// ~ Static fields/initializers
	// =============================================

	/**
	 * static logger for this class
	 */
	private static Logger logger = Logger.getLogger(AddressPanel.class.getName());

	private static final long serialVersionUID = 1L;

	// Tell if we are doing some special actions to the streetname combobox
//	boolean doNothing = false;

	// load the Translatrix
	static {
		Translatrix.addBundle("lu.tudor.santec.gecamed.address.utils.resources.ServerResources");
	}
	// ~ Instance fields
	// ========================================================

	private JTextField tfLocality;
	private JComboBox cbZip;
	private JComboBox cbStreetname;
	private JTextField tfStreetnumber;
	private JComboBox cbCountry;

	/**
	 * listeners need to be deactivated in some cases to avoid listener-loops
	 */
	private boolean listenersActivated = true;

	/**
	 * reference to the address manager bean
	 */
	private AddressManagerInterface manager;

	private Locality currentLocality;

	private String lastSelectedCountry;

	/**
	 * Property that is fired if the panel's data changes
	 */
	public static final String UNSAVED_CHANGES = "UnsavedChanges";

	private static HashMap<Integer, List<Zip>> zipIdMap;

	private static final String EMPTY_ZIP = "----";

	private boolean boldFont = false;

	private LocalitySearchPopup localityPopup;

	private JButton mapsButton;

	/**
	 * Flag to enable and disable auto completion for text field tfLocality
	 */
	private Boolean autocompletionLocality = true;

	/**
	 * builds a new AddressPanel
	 * 
	 * @param simpleView
	 *            for the third party payer field, certical alignment
	 */
	public AddressPanel(boolean simpleView) {
		/* ================================================== */
		this(simpleView, true);
		/* ================================================== */
	}

	/**
	 * builds a new AddressPanel
	 * 
	 * @param simpleView
	 *            for the third party payer field, certical alignment
	 * @param boldFont
	 *            creates bold font labels
	 */
	public AddressPanel(boolean simpleView, boolean boldFont) {
		/* ================================================== */
		this.boldFont = boldFont;
		initComponents();

		if (simpleView) {
			buildSimplePanel();
		} else {
			/* ------------------------------------------------------- */
			this.buildPanel();

			for (Component c : this.getComponents()) {
				((JComponent) c).setOpaque(false);
			}
			/* ------------------------------------------------------- */
		}
		this.setOpaque(false);
		/* ================================================== */
	}

	// ~ Methods
	// ================================================================

	/**
	 * @return
	 */
	public List<Component> getComponentsForFocus() {
		/* ================================================== */
		List<Component> list = new ArrayList<Component>();
		/* ------------------------------------------------------- */
		list.add(cbZip);
		list.add(tfLocality);
		list.add(cbStreetname);
		list.add(tfStreetnumber);
		list.add(cbCountry);
		/* ------------------------------------------------------- */
		return list;
		/* ================================================== */
	}

	/**
	 * adds the components to the panel
	 */
	private void buildPanel() {
		/* ================================================== */

		CellConstraints cc = new CellConstraints();

		// build Layout
		String line = "";
		for (int i = 1; i <= 9; i++) {
			line = line + "25dlu:grow, " + "2dlu" + ", ";
		}

		line = "40dlu, " + "2dlu" + ", " + "65dlu:grow, " + "2dlu" + ", " + "65dlu:grow, " + "2dlu" + ", " + "25dlu, "
				+ "2dlu" + ", " +
				// "60dlu, " + "2dlu";
				"60dlu," + "2dlu" + ", " + "pref";

		FormLayout layout = new FormLayout(line, "BOTTOM:pref");
		this.setLayout(layout);

		this.add(new NamedComponent(Translatrix.getTranslationString("pm.address.zip"), this.cbZip, boldFont),
				cc.xy(1, 1));
		this.add(new NamedComponent(Translatrix.getTranslationString("pm.address.locality"), this.tfLocality, boldFont),
				cc.xy(3, 1));
		this.add(new NamedComponent(Translatrix.getTranslationString("pm.address.streetname"), this.cbStreetname,
				boldFont), cc.xy(5, 1));
		this.add(new NamedComponent(Translatrix.getTranslationString("pm.address.streetnumber"), this.tfStreetnumber,
				boldFont), cc.xy(7, 1));
		this.add(new NamedComponent(Translatrix.getTranslationString("pm.address.country"), this.cbCountry, boldFont),
				cc.xy(9, 1));
		this.add(this.mapsButton, cc.xy(11, 1));

		/* ================================================== */
	}

	/**
	 * Builds a simpler layout
	 */
	private void buildSimplePanel() {
		/* ================================================== */
		CellConstraints cc = new CellConstraints();
		this.setLayout(new FormLayout(// cols
				"right:pref," + "5dlu," + "35dlu," + "60dlu:grow",
				// rows
				"fill:pref," + "3dlu," + "fill:pref," + "3dlu," + "fill:pref," + "3dlu," + "fill:pref," + "3dlu,"
						+ "fill:pref"));
		/* ------------------------------------------------------- */
		// create labels
		JLabel zipLabel = OverviewPanel.createLabel("pm.address.zip");
		JLabel localityLabel = OverviewPanel.createLabel("pm.address.locality");
		JLabel streetLabel = OverviewPanel.createLabel("pm.address.streetname");
		JLabel numberLabel = OverviewPanel.createLabel("pm.address.streetnumber");
		JLabel countryLabel = OverviewPanel.createLabel("pm.address.country");

		/* ------------------------------------------------------- */
		// place labels
		this.add(zipLabel, cc.xy(1, 1));
		this.add(localityLabel, cc.xy(1, 3));
		this.add(streetLabel, cc.xy(1, 5));
		this.add(numberLabel, cc.xy(1, 7));
		this.add(countryLabel, cc.xy(1, 9));

		/* ------------------------------------------------------- */
		// place fields
		this.add(this.cbZip, cc.xy(3, 1));
		this.add(this.tfLocality, cc.xyw(3, 3, 2));
		this.add(this.cbStreetname, cc.xyw(3, 5, 2));
		this.add(this.tfStreetnumber, cc.xy(3, 7));
		this.add(this.cbCountry, cc.xyw(3, 9, 2));
		/* ================================================== */
	}

	/**
	 * initializes the Components
	 */
	private void initComponents() {
		/* ================================================== */
		// Get the reference to the address manager
		/* ------------------------------------------------------- */
		try {
			manager = (AddressManagerInterface) ManagerFactory.getRemote(AddressManagerBean.class);
		} catch (Exception e) {
			MainFrame.reportServerError(e);
		}
		/* ------------------------------------------------------- */
		// create the zip combobox and fill it with the lux codes
		/* ------------------------------------------------------- */
		this.cbZip = new JComboBox();
		AutoCompletion.enableWithFreeText(this.cbZip);
		/* ------------------------------------------------------- */
		// get the codes
		/* ------------------------------------------------------- */
		fetchAllZipcodes();
		this.cbZip.addItemListener(this);

		this.tfLocality = new LimitTextField(50);
		this.tfLocality.setEditable(true);
		this.tfLocality.addKeyListener(this);

		this.localityPopup = new LocalitySearchPopup();
		this.localityPopup.addFocusListener(new FocusAdapter() {
			@Override
			public void focusLost(FocusEvent e) {
				localityPopup.setVisible(false);
				super.focusLost(e);
			}
		});

		this.localityPopup.addPopupMenuListener(new PopupMenuListener() {

			public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
			}

			public void popupMenuCanceled(PopupMenuEvent e) {
			}

			public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
				Locality l = (Locality) localityPopup.list.getSelectedValue();
				if (l != null) {
					try {
						currentLocality = l;
						if (currentLocality != null) {
							fetchStreetnamesByLocality();
							tfLocality.setText(currentLocality.getName());
						}
						tfLocality.transferFocus();
					} catch (Exception ee) {
						ee.printStackTrace();
					}
				} // else
					// autocompletion = true;
			}

		});

		this.cbStreetname = new JComboBox();
		// this.cbStreetname.setEditable(true);
		this.cbStreetname.setEditable(true);
		AutoCompletion.enableWithFreeText(this.cbStreetname);
		((JTextField) this.cbStreetname.getEditor().getEditorComponent()).addKeyListener(this);

		this.cbStreetname.addItemListener(this);

		this.tfStreetnumber = new LimitTextField(10);
		this.tfStreetnumber.addKeyListener(this);
		this.tfStreetnumber.addFocusListener(new FocusAdapter() {
			@Override
			public void focusLost(FocusEvent e) {
				selectZIPbyLocalityAndStreet();
				super.focusLost(e);
			}
		});

		this.cbCountry = new JComboBox(GECAMedGuiUtils.getTranslatedList(Country.class));
		this.cbCountry.addItemListener(this);

		setCountry(Country.LUXEMBOURG);
		AutoCompletion.enableWithFreeText(this.cbCountry);
		
		/* ------------------------------------------------------- */
		// make font plain
		/* ------------------------------------------------------- */
		if (!this.boldFont) {
			/* ------------------------------------------------------- */
			BoldComboboxRenderer plainRenderer = new BoldComboboxRenderer();
			cbZip.setRenderer(plainRenderer);
			cbCountry.setRenderer(plainRenderer);
			cbStreetname.setRenderer(plainRenderer);
			cbZip.setFont(GECAMedFonts.PLAIN_LABEL_FONT);
			cbCountry.setFont(GECAMedFonts.PLAIN_LABEL_FONT);
			cbStreetname.setFont(GECAMedFonts.PLAIN_LABEL_FONT);
			/* ------------------------------------------------------- */
		}
		mapsButton = ButtonFactory.createNarrowButton(IconFetcher.getSmallIcon(GECAMedModule.class, GECAMedModule.SERVER));
		mapsButton.setToolTipText(Translatrix.getTranslationString("MapOpener.openMaps"));
		mapsButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				StringBuffer address = new StringBuffer();
				address.append(getStreetNumber()).append(" ").append(getStreetName()).append(" ").append(getZip())
						.append(" ").append(getTfLocality()).append(", ").append(getCountry());
				MapOpener.openMap(address.toString());
			}
		});
		/* ================================================== */
	}

	public void keyTyped(KeyEvent e) {
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.awt.event.KeyListener#keyReleased(java.awt.event.KeyEvent)
	 */
	public void keyPressed(KeyEvent e) {
		// if listeners are activ, fire property change
		if (listenersActivated) {
			if (e.getSource().equals(this.tfLocality)) {
				this.currentLocality = null;
				if (e.getKeyCode() == KeyEvent.VK_DOWN) {

					this.localityPopup.list.setSelectedIndex(this.localityPopup.list.getSelectedIndex() + 1);
					e.consume();
				} else if (e.getKeyCode() == KeyEvent.VK_UP) {
					int selectIndex = this.localityPopup.list.getSelectedIndex();
					if (selectIndex == 0)
						this.localityPopup.list.clearSelection();
					else
						this.localityPopup.list.setSelectedIndex(this.localityPopup.list.getSelectedIndex() - 1);
					e.consume();
				} else if (e.getKeyCode() == KeyEvent.VK_ENTER) {
					this.localityPopup.setVisible(false);
				}
			}

		}
		if ((e.getKeyCode() == KeyEvent.VK_A) && ((e.getModifiers() & KeyEvent.CTRL_MASK) != 0)) {

//			doNothing = true;
		} else if ((e.getKeyCode() == KeyEvent.VK_UP) || (e.getKeyCode() == KeyEvent.VK_DOWN)
				|| (e.getKeyCode() == KeyEvent.VK_LEFT) || (e.getKeyCode() == KeyEvent.VK_RIGHT)) {
//			doNothing = true;
		}
		else {
//			doNothing = false;

		}

	}

	public void keyReleased(KeyEvent e) {
		if (listenersActivated) {
			if (e.getSource().equals(this.tfLocality)) {
				this.currentLocality = null;
				if (e.getKeyCode() == KeyEvent.VK_DOWN) {
					e.consume();
				} else if (e.getKeyCode() == KeyEvent.VK_UP) {
					e.consume();
				} else if (e.getKeyCode() == KeyEvent.VK_ENTER) {
				} else {
					fetchLocalitiesbySearchString();
				}
			}

//			if (!doNothing) {
//				JTextField tf = (JTextField) cbStreetname.getEditor().getEditorComponent();
//				int pos = tf.getCaretPosition();
//				cbStreetname.setSelectedItem(((JTextField) cbStreetname.getEditor().getEditorComponent()).getText());
//				JTextComponent editor = ((JTextField) cbStreetname.getEditor().getEditorComponent());
//				editor.setCaretPosition(pos);
//				editor.moveCaretPosition(pos);
//			}
			
			firePropertyChange(UNSAVED_CHANGES, false, true);
		}

	}

	private void fetchLocalitiesbySearchString() {
		listenersActivated = false;
		try {
			Collection<Locality> localities = manager.searchLocality(tfLocality.getText());
			if (this.autocompletionLocality) {
				this.localityPopup.setLocalities(localities);
				this.localityPopup.setPopupSize(tfLocality.getWidth(), 200);
				this.localityPopup.show(this.tfLocality, 0, tfLocality.getHeight());
				this.localityPopup.setVisible(true);
				this.localityPopup.list.clearSelection();
			}
		} catch (Exception e) {
			logger.error("Error while fetching localities", e);
		} finally {
			listenersActivated = true;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * java.awt.event.ItemListener#itemStateChanged(java.awt.event.ItemEvent)
	 */
	public void itemStateChanged(ItemEvent e) {

		// if listeners are activ, fire property change
		if (listenersActivated) {

			if (e.getSource().equals(this.cbCountry)) {
				ComboBoxElement<Country> selectedItem = getSelectedCountry();
				String selectedCountry = selectedItem == null ? null : String.valueOf(selectedItem);

				if (e.getStateChange() == ItemEvent.DESELECTED) {
					return;
				} else {
					if ((lastSelectedCountry == null && selectedCountry == null)
							|| (lastSelectedCountry != null && lastSelectedCountry.equals(selectedCountry)))
						return;
				}
				lastSelectedCountry = selectedCountry;

				try {
					listenersActivated = false;
					// country box fill zip box if country is luxembourg
					if (isLux(selectedItem)) {
						this.autocompletionLocality = true;
						fetchAllZipcodes();
					} else {
						this.autocompletionLocality = false;
						Object saveObject = cbZip.getSelectedItem();
						cbZip.removeAllItems();
						cbZip.setSelectedItem(saveObject);
						saveObject = cbStreetname.getSelectedItem();
						cbStreetname.removeAllItems();
						cbStreetname.setSelectedItem(saveObject);
					}
				} finally {
					listenersActivated = true;
				}
			} else if (e.getSource().equals(this.cbZip)) {
				if (e.getStateChange() == ItemEvent.DESELECTED)
					return;
				// zip box autocomplete location and streed if country is luxembourg
				try {
					ComboBoxElement<Country> selectedCountry = getSelectedCountry();
					listenersActivated = false;
					if (isLux(selectedCountry) && !EMPTY_ZIP.equals(cbZip.getSelectedItem())) {
						fetchStreetnamesByZIP();
						fetchLocalityByZIP();
					}
				} finally {
					listenersActivated = true;
				}
			} else if (e.getSource().equals(cbStreetname)) {

				if (e.getStateChange() == ItemEvent.DESELECTED)
					return;

				selectZIPbyLocalityAndStreet();
			}
			firePropertyChange(UNSAVED_CHANGES, false, true);
		}
	}

	private boolean isLux(Object countryItem) {
		if (countryItem == null) return false;
		
		try {
			if (countryItem instanceof ComboBoxElement<?>) {
				Country c = (Country) ((ComboBoxElement<?>)countryItem).getValue();
				if (c.getName() != null) {
					if (c.getName().toLowerCase().equals(Country.LUXEMBOURG)) {
						return true;
					}
				}
			} else if (countryItem instanceof Country) {
				Country c = (Country) countryItem;
				if (c.getName() != null) {
					if (c.getName().toLowerCase().equals(Country.LUXEMBOURG)) {
						return true;
					}
				}
			} else if (countryItem instanceof String) {
				if (((String)countryItem).toLowerCase().equals(Country.LUXEMBOURG)) {
					return true;
				}
			}	
		} catch (Exception e) {}
		
		return false;
	}

	/**
	 * fetches the zipcodes, stores them in the GECAMedLists and adds them to
	 * the zip combobox
	 */
	private void fetchAllZipcodes() {
		try {
			Object saveObject = cbZip.getSelectedItem();
			cbZip.removeAllItems();
			cbZip.addItem(EMPTY_ZIP);
			List<Zip> zips = GECAMedLists.getListReference(Zip.class);
			ArrayList<Integer> helpZips = new ArrayList<Integer>();

			for (Zip zip : zips) {
				if (!helpZips.contains(zip.getZip())) {
					helpZips.add(zip.getZip());
					cbZip.addItem(zip);
				}
			}
			cbZip.setSelectedItem(saveObject);
		} catch (Exception e1) {
			logger.log(Level.WARN, "initializing zips failed", e1);
		}
	}

	/**
	 * fetches the valid lux streetnames for the selected zip code and fills
	 * them into the streetname combobox
	 */
	private void fetchStreetnamesByZIP() {
		try {
			Zip zipCode = (Zip) cbZip.getSelectedItem();
			if (zipCode != null && !EMPTY_ZIP.equals(zipCode.toString())) {
				String[] streets = manager.getStreetByZip(zipCode.getZip());
				cbStreetname.removeAllItems();
				for (int i = 0; i < streets.length; i++) {
					cbStreetname.addItem(streets[i]);
				}
			}
		} catch (Exception e1) {
			cbStreetname.removeAllItems();
			cbStreetname.addItem("");
		}
	}

	/**
	 * fetches the valid lux streetnames for the selected zip code and fills
	 * them into the streetname combobox
	 */
	private void fetchStreetnamesByLocality() {
		try {
			if (currentLocality != null) {
				String[] streets = manager.getStreetLocalityID(currentLocality.getId());
				cbStreetname.removeAllItems();
				for (int i = 0; i < streets.length; i++) {
					cbStreetname.addItem(streets[i]);
				}
			}
		} catch (Exception e1) {
			cbStreetname.removeAllItems();
			cbStreetname.addItem("");
		}
	}

	/**
	 * fetches the valid lux loation for the selected zip code and fills it into
	 * the locality textfield
	 */
	private void fetchLocalityByZIP() {
		try {
			Zip zipCode = (Zip) cbZip.getSelectedItem();
			if (zipCode != null) {
				currentLocality = GECAMedLists.getIdMapReference(Locality.class).get(zipCode.getLocalityId());
				this.tfLocality.setText(currentLocality.getName());
			}
		} catch (Exception e1) {
			this.currentLocality = null;
			this.tfLocality.setText("");
		}
	}

	private void selectZIPbyLocalityAndStreet() {
		try {
			if (currentLocality == null || cbStreetname.getSelectedItem() == null)
				return;

			String streetNumber = tfStreetnumber.getText();
			if (streetNumber != null) {
				streetNumber = streetNumber.replaceAll("\\D", "");
			}
			
			Zip zipCode = manager.getZipByLocalityAndStreet(currentLocality, (String) cbStreetname.getSelectedItem(),streetNumber);
			try {
				listenersActivated = false;
				if (zipCode != null)
					cbZip.setSelectedItem(zipCode);
				// else
				// cbZip.setSelectedIndex(0);
			} finally {
				listenersActivated = true;
			}
		} catch (Exception e) {
			logger.error("Error while fetching ZIP codes", e);
		}
	}

	/**
	 * sets the zip to the addresspanel. if zip is a valix lux code, the panel
	 * will be autocompleted
	 * 
	 * @param zipcode
	 */
	public void setZip(String zipcode) {
		listenersActivated = false;
		try {
			if (zipcode == null || zipcode.equals("")) {
				try {
					this.cbZip.setSelectedIndex(0);
				} catch (Exception e) {
				}
				return;
			}
			Integer code = null;
			try {
				code = Integer.parseInt(zipcode);
			} catch (Exception e) {
			}
			List<Zip> zip = getZip(code);
			if (zip != null && zip.size() != 0) {
				this.cbZip.setSelectedItem(zip.get(0));
			} else {
				try {
					this.cbZip.addItem(zipcode);
					this.cbZip.setSelectedItem(zipcode);
				} catch (Exception e) {
				}
			}
		} finally {
			listenersActivated = true;
		}
	}

	/**
	 * returns the selected zip as String
	 * 
	 * @return
	 */
	public String getZip() {
		try {
			return ((Zip) this.cbZip.getSelectedItem()).getZip() + "";
		} catch (Exception e) {
			try {
				return this.cbZip.getSelectedItem().toString();
			} catch (Exception e2) {
				return EMPTY_ZIP;
			}
		}
	}

	/**
	 * sets the localityID to the addresspanel. the name of the locality is
	 * autocompleted from the db
	 * 
	 * @param localityID
	 */
	public void setLocalityID(Integer localityID) {
		listenersActivated = false;
		try {
			if (localityID == null) {
				this.currentLocality = null;
				this.tfLocality.setText("");
				return;
			}

			try {
				this.currentLocality = GECAMedLists.getIdMapReference(Locality.class).get(localityID);
				this.tfLocality.setText(currentLocality.getName());
			} catch (Exception e) {
				this.currentLocality = null;
				this.tfLocality.setText("");
				logger.log(Level.INFO, "no locality for ID " + localityID, e);
			}
		} finally {
			listenersActivated = true;
		}
	}

	/**
	 * returns the db id of the selected locality
	 * 
	 * @return
	 */
	public Integer getLocalityID() {
		try {
			return this.currentLocality.getId();
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * sets the name of the locality
	 * 
	 * @param locality
	 */
	public void setLocality(String locality) {
		listenersActivated = false;
		try {
			this.tfLocality.setText(locality);
		} finally {
			listenersActivated = true;
		}
	}

	/**
	 * returns the name of the locality as String
	 * 
	 * @return
	 */
	public String getTfLocality() {
		Object item = this.tfLocality.getText();
		if (item instanceof String) {
			return (String) this.tfLocality.getText();
		} else if (item instanceof Locality) {
			return ((Locality) item).getName();
		}
		return "";
	}

	/**
	 * sets the streetname and number to the panel
	 * 
	 * @param street
	 * @param number
	 */
	public void setStreet(String street, String number) {
		listenersActivated = false;
		try {
			try {
				this.cbStreetname.setSelectedItem(street);
			} catch (Exception e) {
				try {
					((JTextField) this.cbStreetname.getEditor().getEditorComponent()).setText(street);
				} catch (Exception ee) {
				}
			}
			this.tfStreetnumber.setText(number);
		} catch (Exception eee) {
		} finally {
			listenersActivated = true;
		}
	}

	/**
	 * returns the streetname
	 * 
	 * @return
	 */
	public String getStreetName() {
		try {
			return this.cbStreetname.getSelectedItem().toString();
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * returns the streetnumber
	 * 
	 * @return
	 */
	public String getStreetNumber() {
		return this.tfStreetnumber.getText();
	}

	/**
	 * sets the country to the panel
	 * 
	 * @param country
	 */
	public void setCountry(String country) {
		ComboBoxElement<Country> selectedItem;

		if (country == null) {
			this.cbCountry.setSelectedIndex(GECAMedGuiUtils.getIndexByDefaultTranslation(cbCountry, Country.LUXEMBOURG));
			return;
		}

		try {
			if (!country.equals(String.valueOf(cbCountry.getSelectedItem()))) {
				listenersActivated = false;
				try {
					// this.cbCountry.setSelectedItem(country);
					ComboBoxElement<Country> item;
					int selectedIndex = -1;
					for (int index = 0; selectedIndex < 0 && index < cbCountry.getModel().getSize(); index++) {
						item = (ComboBoxElement<Country>) cbCountry.getItemAt(index);
						if (country.equalsIgnoreCase(item.getValue().getName()))
							selectedIndex = index;
					}
					selectedItem = getSelectedCountry();
					lastSelectedCountry = selectedItem == null ? null : String.valueOf(selectedItem);
					cbCountry.setSelectedIndex(selectedIndex);
					selectedItem = (ComboBoxElement<Country>) cbCountry.getSelectedItem();

				} catch (Exception e) {
					logger.log(Level.WARN, "setting country failed", e);
				}

				try {
					if (cbCountry.getSelectedIndex() < 0) {
						Country c = new Country();
						c.setName(country);
						ComboBoxElement<Country> elem = new ComboBoxElement<Country>(c.getDefaultTranslation(), c);
						cbCountry.addItem(elem);
						cbCountry.setSelectedItem(elem);
					}
				} catch (Exception e) {
					logger.log(Level.WARN, "setting country failed", e);
				}

				// country box fill zip box if country is luxembourg
				if (isLux(getSelectedCountry())) {
					this.autocompletionLocality = true;
					fetchAllZipcodes();
				} else { this.autocompletionLocality = false; Object
					 saveObject = cbZip.getSelectedItem(); cbZip.removeAllItems();
					 cbZip.setSelectedItem(saveObject); saveObject =
					 cbStreetname.getSelectedItem();
					 cbStreetname.removeAllItems();
					 cbStreetname.setSelectedItem(saveObject); }
				}
		} finally {
			listenersActivated = true;
		}
	}

	/**
	 * returns the selected country
	 * 
	 * @return
	 */
	public String getCountry() {
		try {
			Object country = cbCountry.getSelectedItem();
			if (country instanceof ComboBoxElement)
				return String.valueOf(((ComboBoxElement) country).getValue());
			return String.valueOf(country);
		} catch (Exception e) {
			return null;
		}

	}

	/**
	 * enables/disables the editing of the panel
	 * 
	 * @param editable
	 */
	public void setEditable(boolean editable) {
		this.cbZip.setEnabled(editable);
		this.tfLocality.setEditable(editable);
		this.cbStreetname.setEnabled(editable);
		this.tfStreetnumber.setEditable(editable);
		this.cbCountry.setEnabled(editable);
	}

	class BoldComboboxRenderer extends DefaultComboBoxRenderer {

		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		/*
		 * (non-Javadoc)
		 * 
		 * @seelu.tudor.santec.gecamed.core.gui.utils.DefaultComboBoxRenderer#
		 * getListCellRendererComponent(javax.swing.JList, java.lang.Object,
		 * int, boolean, boolean)
		 */
		@Override
		public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
				boolean cellHasFocus) {
			/* ====================================================== */
			super.setFont(GECAMedFonts.BOLD_LABEL_FONT);
			return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
			/* ====================================================== */
		}
	}

	private static List<Zip> getZip(Integer code) {
		if (zipIdMap == null) {
			reloadZipIdMap();
			GECAMedLists.addPropertyChangeListener(new PropertyChangeListener() {
				public void propertyChange(PropertyChangeEvent evt) {
					reloadZipIdMap();
				}
			}, Zip.class);
		}

		return zipIdMap.get(code);
	}

	private static void reloadZipIdMap() {
		List<Zip> allZips = GECAMedLists.getListReference(Zip.class);
		List<Zip> zips;

		zipIdMap = new HashMap<Integer, List<Zip>>();
		for (Zip zip : allZips) {
			zips = zipIdMap.get(zip.getZip());
			if (zips == null) {
				zips = new LinkedList<Zip>();
				zipIdMap.put(zip.getZip(), zips);
			}
			zips.add(zip);
		}
	}

	private ComboBoxElement<Country> getSelectedCountry() {
		Object item = cbCountry.getSelectedItem();
		ComboBoxElement<Country> cbe;

		if (item == null)
			cbe = null;
		else if (item instanceof ComboBoxElement)
			cbe = (ComboBoxElement<Country>) item;
		else if (item instanceof String) {
			cbe = null;
			String countryName = (String) item;
			for (int index = 0; index < cbCountry.getItemCount(); index++) {
				item = cbCountry.getItemAt(index);
				if (item == null)
					continue;

				boolean matches = item.toString().equalsIgnoreCase(countryName);
				if (!matches && item instanceof ComboBoxElement) {
					Object value = ((ComboBoxElement<?>) item).getValue();
					matches = value != null && value.toString().equalsIgnoreCase(countryName);
				}

				if (matches) {
					if (item instanceof ComboBoxElement) {
						cbCountry.setSelectedIndex(index);
						cbe = (ComboBoxElement<Country>) cbCountry.getSelectedItem();
					}
					break;
				}
			}

			if (cbe == null) {
				Country country = new Country();
				country.setName(countryName);
				cbe = new ComboBoxElement<Country>(countryName, country);

				try {
					listenersActivated = false;
					cbCountry.removeItem(item);
					cbCountry.addItem(cbe);
				} finally {
					listenersActivated = true;
				}
			}
		} else {
			logger.error("Class " + item.getClass() + " is not allowed in country combobox.");
			cbe = null;
		}

		return cbe;
	}
}
