/*******************************************************************************
 * Copyright (c) 2000, 2006 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.ui.examples.propertysheet;

import java.util.Vector;

import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.ui.views.properties.ComboBoxPropertyDescriptor;
import org.eclipse.ui.views.properties.IPropertyDescriptor;
import org.eclipse.ui.views.properties.IPropertySource;
import org.eclipse.ui.views.properties.PropertyDescriptor;
import org.eclipse.ui.views.properties.TextPropertyDescriptor;

/**
 * Example IPropertySource who itself is NOT editable, but whose children are.
 * The values of the children determine the value of the address.
 */
public class Address implements IPropertySource {

	//properties
	private String city;

	private Integer province;

	private String postalCode;

	private StreetAddress street;

	//Property unique keys
	public static final String P_ID_STREET = "Address.Street"; //$NON-NLS-1$

	public static final String P_ID_CITY = "Address.City"; //$NON-NLS-1$

	public static final String P_ID_PROVINCE = "Address.Province"; //$NON-NLS-1$

	public static final String P_ID_POSTALCODE = "Address.PostalCode"; //$NON-NLS-1$

	//Property display keys
	public static final String P_STREET = MessageUtil.getString("Street"); //$NON-NLS-1$

	public static final String P_CITY = MessageUtil.getString("City"); //$NON-NLS-1$

	public static final String P_PROVINCE = MessageUtil.getString("Province"); //$NON-NLS-1$

	public static final String P_POSTALCODE = MessageUtil
			.getString("PostalCode"); //$NON-NLS-1$

	public static final String P_DESCRIPTORS = "properties"; //$NON-NLS-1$

	//default values
	private static final StreetAddress STREET_DEFAULT = new StreetAddress();

	private static final String CITY_DEFAULT = MessageUtil
			.getString("unspecified_city"); //$NON-NLS-1$

	private static final Integer PROVINCE_DEFAULT = Integer.valueOf(0);

	private static final String POSTALCODE_DEFAULT = "A1B2C3"; //$NON-NLS-1$

	//
	static private class ProvinceLabelProvider extends LabelProvider {
		@Override
		public String getText(Object element) {
			String[] provinceValues = new String[] {
					MessageUtil.getString("British_Columbia"), MessageUtil.getString("Alberta"), MessageUtil.getString("Saskatchewan"), MessageUtil.getString("Manitoba"), MessageUtil.getString("Ontario"), MessageUtil.getString("Quebec"), MessageUtil.getString("Newfoundland"), MessageUtil.getString("Prince_Edward_Island"), MessageUtil.getString("Nova_Scotia"), MessageUtil.getString("New_Brunswick"), MessageUtil.getString("Yukon"), MessageUtil.getString("North_West_Territories"), MessageUtil.getString("Nunavut") }; //$NON-NLS-13$ //$NON-NLS-12$ //$NON-NLS-11$ //$NON-NLS-10$ //$NON-NLS-9$ //$NON-NLS-8$ //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$
			return provinceValues[((Integer) element).intValue()];
		}
	}

	private static Vector<PropertyDescriptor> descriptors;

	private static String[] provinceValues;
	static {
		descriptors = new Vector<>();
		provinceValues = new String[] {
				MessageUtil.getString("British_Columbia"), MessageUtil.getString("Alberta"), MessageUtil.getString("Saskatchewan"), MessageUtil.getString("Manitoba"), MessageUtil.getString("Ontario"), MessageUtil.getString("Quebec"), MessageUtil.getString("Newfoundland"), MessageUtil.getString("Prince_Edward_Island"), MessageUtil.getString("Nova_Scotia"), MessageUtil.getString("New_Brunswick"), MessageUtil.getString("Yukon"), MessageUtil.getString("North_West_Territories"), MessageUtil.getString("Nunavut") }; //$NON-NLS-13$ //$NON-NLS-12$ //$NON-NLS-11$ //$NON-NLS-10$ //$NON-NLS-9$ //$NON-NLS-8$ //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$
		descriptors.addElement(new PropertyDescriptor(P_ID_STREET, P_STREET));
		descriptors.addElement(new TextPropertyDescriptor(P_ID_CITY, P_CITY));

		//PostalCode
		PropertyDescriptor propertyDescriptor = new TextPropertyDescriptor(
				P_ID_POSTALCODE, P_POSTALCODE);
		//add custom validator to propertyDescriptor limiting postalcode
		//values to XYXYXY, where X is a letter and Y is a digit
		propertyDescriptor.setValidator(value -> {
			if (value == null)
				return MessageUtil.getString("postal_code_is_incomplete"); //$NON-NLS-1$

			//
			String testPostalCode = ((String) value).toUpperCase();
			final int length = testPostalCode.length();
			final char space = ' ';

			//removes white space
			StringBuilder postalCodeBuffer = new StringBuilder(6);
			char current;
			for (int i = 0; i < length; i++) {
				current = testPostalCode.charAt(i);
				if (current != space)
					postalCodeBuffer.append(current);
			}
			testPostalCode = postalCodeBuffer.toString();

			//check for proper length
			if (testPostalCode.length() != 6) {
				return MessageUtil.getString("postal_code_is_incomplete"); //$NON-NLS-1$
			}

			//check for proper format
			if (testPostalCode.charAt(1) < '0'
					|| testPostalCode.charAt(1) > '9'
					|| testPostalCode.charAt(3) < '0'
					|| testPostalCode.charAt(3) > '9'
					|| testPostalCode.charAt(5) < '0'
					|| testPostalCode.charAt(5) > '9'
					|| testPostalCode.charAt(0) < 'A'
					|| testPostalCode.charAt(0) > 'Z'
					|| testPostalCode.charAt(2) < 'A'
					|| testPostalCode.charAt(2) > 'Z'
					|| testPostalCode.charAt(4) < 'A'
					|| testPostalCode.charAt(4) > 'Z') {
				//fail
				return MessageUtil
						.format(
								"_is_an_invalid_format_for_a_postal_code", new Object[] { testPostalCode }); //$NON-NLS-1$
			}

			//all pass
			return null;
		});
		descriptors.addElement(propertyDescriptor);

		//
		ComboBoxPropertyDescriptor desc = new ComboBoxPropertyDescriptor(
				P_ID_PROVINCE, P_PROVINCE, provinceValues);
		desc.setLabelProvider(new ProvinceLabelProvider());
		descriptors.addElement(desc);
	}

	/**
	 * Address Default Constructor
	 */
	Address() {
		super();
	}

	/**
	 * Creates a new address.
	 *
	 * @param street the street
	 * @param city the city
	 * @param province the province
	 * @param postalCode has the form XYXYXY: where X is a letter and Y is a digit
	 * @exception IllegalArgumentException if postalcode not in above form
	 */
	public Address(StreetAddress street, String city, Integer province,
			String postalCode) {
		super();
		setStreet(street);
		setCity(city);
		setPostalCode(postalCode);
		setProvince(province);
	}

	/**
	 * Returns the city
	 */
	private String getCity() {
		if (city == null)
			city = CITY_DEFAULT;
		return city;
	}

	/**
	 * Standard Accessor
	 */
	private static Vector<PropertyDescriptor> getDescriptors() {
		return descriptors;
	}

	@Override
	public Object getEditableValue() {
		return this.toString();
	}

	/**
	 * Returns the postal code
	 */
	private String getPostalCode() {
		if (postalCode == null)
			postalCode = POSTALCODE_DEFAULT;
		return postalCode;
	}

	/* (non-Javadoc)
	 * Method declared on IPropertySource
	 */
	@Override
	public IPropertyDescriptor[] getPropertyDescriptors() {
		return getDescriptors().toArray(
				new IPropertyDescriptor[getDescriptors().size()]);
	}

	/**
	 * The <code>Address</code> implementation of this
	 * <code>IPropertySource</code> method returns the following properties
	 *
	 * 	1) P_CITY returns java.lang.String
	 * 	2) P_POSTALCODE returns java.lang.String
	 *  3) P_PROVINCE returns java.lang.String
	 *	4) P_STREET returns StreetAddress
	 */
	@Override
	public Object getPropertyValue(Object propKey) {
		if (propKey.equals(P_ID_PROVINCE))
			return getProvince();
		if (propKey.equals(P_ID_STREET))
			return getStreet();
		if (propKey.equals(P_ID_CITY))
			return getCity();
		if (propKey.equals(P_ID_POSTALCODE))
			return getPostalCode();
		return null;
	}

	/**
	 * Returns the province
	 */
	private Integer getProvince() {
		if (province == null)
			province = PROVINCE_DEFAULT;
		return province;
	}

	/**
	 * Returns the street
	 */
	public StreetAddress getStreet() {
		if (street == null)
			street = new StreetAddress();
		return street;
	}

	/* (non-Javadoc)
	 * Method declared on IPropertySource
	 */
	@Override
	public boolean isPropertySet(Object property) {
		if (property.equals(P_ID_PROVINCE))
			return getProvince() != PROVINCE_DEFAULT;
		if (property.equals(P_ID_STREET))
			return !STREET_DEFAULT.equals(getStreet());
		if (property.equals(P_ID_CITY))
			return getCity() != CITY_DEFAULT;
		if (property.equals(P_ID_POSTALCODE))
			return getPostalCode() != POSTALCODE_DEFAULT;
		return false;
	}

	/* (non-Javadoc)
	 * Method declared on IPropertySource
	 */
	@Override
	public void resetPropertyValue(Object property) {
		if (P_ID_POSTALCODE.equals(property)) {
			setPostalCode(POSTALCODE_DEFAULT);
			return;
		}
		if (P_ID_CITY.equals(property)) {
			setCity(CITY_DEFAULT);
			return;
		}
		if (P_ID_PROVINCE.equals(property)) {
			setProvince(PROVINCE_DEFAULT);
			return;
		}
		if (P_ID_STREET.equals(property)) {
			setStreet(new StreetAddress());
			return;
		}
	}

	/**
	 * Sets the city
	 */
	private void setCity(String newCity) {
		city = newCity;
	}

	/**
	 * Sets the postal code
	 */
	private void setPostalCode(String newPostalCode) {
		//validation in ICellEditorValidator registered in PropertyDescriptor
		this.postalCode = newPostalCode.toUpperCase();
	}

	/**
	 * The <code>Address</code> implementation of this
	 * <code>IPropertySource</code> method
	 * defines the following Setable properties
	 *
	 * 	1) P_CITY expects java.lang.String
	 * 	2) P_POSTALCODE expects java.lang.String
	 *  3) P_PROVINCE expects java.lang.String
	 *
	 * <p>P_ID_STREET is not set here since it is referenced
	 * and set directly in StreetAddress.
	 * According to IPropertySource, StreetAddress.getEditableValue
	 * should return a String which will be passed to this method
	 * as the value. A new StreetAddress object should then be
	 * created from the string.
	 * An alternative would be to return the StreetAddress
	 * directly in StreetAddress.getEditableValue and define a
	 * cell editor for the StreetAddress property.
	 * This was ommitted for the sake of simplicity.
	 */
	@Override
	public void setPropertyValue(Object name, Object value) {
		if (P_ID_POSTALCODE.equals(name)) {
			setPostalCode((String) value);
			return;
		}
		if (P_ID_CITY.equals(name)) {
			setCity((String) value);
			return;
		}
		if (P_ID_PROVINCE.equals(name)) {
			setProvince((Integer) value);
			return;
		}
	}

	/**
	 * Sets the province
	 */
	private void setProvince(Integer newProvince) {
		province = newProvince;
	}

	/**
	 * Sets the street
	 */
	private void setStreet(StreetAddress newStreet) {
		street = newStreet;
	}

	/**
	 * The value as displayed in the Property Sheet.
	 * @return java.lang.String
	 */
	@Override
	public String toString() {
		StringBuilder outStringBuilder = new StringBuilder();
		final String comma_space = ", "; //$NON-NLS-1$
		final String space = " "; //$NON-NLS-1$
		if (!getStreet().equals(STREET_DEFAULT)) {
			outStringBuilder.append(getStreet());
			outStringBuilder.append(comma_space);
		}

		outStringBuilder.append(getCity());
		outStringBuilder.append(space);
		outStringBuilder.append(provinceValues[getProvince().intValue()]);
		outStringBuilder.append(comma_space);
		outStringBuilder.append(getPostalCode());

		return outStringBuilder.toString();
	}
}
