/*
 * Created on Aug 17, 2004
 *
 * TODO To change the template for this generated file go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
package hibergui;

import static hibergui.HiberGUI.*;
import hibergui.event.ChangeEvent;
import hibergui.event.FocusAdapter;
import hibergui.event.FocusEvent;
import hibergui.event.KeyAdapter;
import hibergui.event.KeyEvent;
import hibergui.event.ModificationEvent;
import hibergui.event.ModificationListener;
import hibergui.event.TraverseEvent;
import hibergui.event.TraverseListener;
import hibergui.verify.Constraint;
import hibergui.verify.SelfConstraint;
import hibergui.verify.SelfConstraintAdapter;
import hibergui.verify.SelfVerifier;
import hibergui.verify.VerificationError;
import hibergui.verify.VerificationException;

import java.util.ArrayList;
import java.util.List;

import static org.eclipse.swt.SWT.*;

import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.Composite;

/**
 * Common superclass of HiberGUI components for editing properties of business
 * entities.
 * 
 * @author Miklós Espák
 */
public abstract class Edit<T> extends Component<T> {

	/**
	 * The actual value of the edit.
	 */
	protected T value;

	/**
	 * The form which the edit is placed on.
	 */
	protected final Form<?> form;
	private final String prop, name;

	private boolean dirty, invalid, dirty2, readonly, fixed;

	/**
	 * This SWT listener fires a HiberGUI traverse event when an SWT traverse
	 * event is fired. The listener has to be added to the internal controls of
	 * HiberGUI components. Not working yet.
	 */
	protected org.eclipse.swt.events.TraverseListener lsn_TRAVERSE = new org.eclipse.swt.events.TraverseListener() {
		public void keyTraversed(org.eclipse.swt.events.TraverseEvent e) {
			System.out.println("Component.enclosing_method() lsnTraverse " + e);
			// fireTraversal(Edit.this, new TraverseEvent<Edit>(Edit.this, e));
		}
	};

	private List<TraverseListener> traverseListeners = new ArrayList<TraverseListener>();

	public final void addTraverseListener(TraverseListener lsn) {
		traverseListeners.add(lsn);
	}

	public final void removeTraverseListener(TraverseListener lsn) {
		traverseListeners.remove(lsn);
	}

	protected void fireTraversal(Edit<T> edit, TraverseEvent<?> event) {
	}

	/**
	 * Constructs an edit.
	 * 
	 * @param form
	 *            The form which the edit belongs to.
	 * @param cptParent
	 *            The composite which the edit will be placed on.
	 * @param style
	 *            The style of the edit. It is a combination of constants
	 *            defined in <a>hibergui.HiberGUI</a>.
	 * @param prop
	 *            The name of the property of the business entity class which
	 *            the edit should handle.
	 * @param name
	 *            The name of the edit. It may appear in GUI messages.
	 */
	protected Edit(final Form<?> form, Composite cptParent, int style,
			String prop, String name) {
		super(cptParent, style);

		this.form = form;
		this.prop = prop;
		this.name = name;

		form.addEdit(this);

		addFocusListener(new FocusAdapter() {
			@Override
			public void focusLost(FocusEvent<?> e) {
				if (dirty)
					set(extract());
			}
		});
		addKeyListener(new KeyAdapter() {
			@Override
			public void keyPressed(KeyEvent e) {
				switch (e.wrapped.keyCode) {
				case ESC:
					fill();
					markClean();
					break;
				case '\r':
					if (dirty)
						set(extract());
					break;
				}
			}
		});
		addTraverseListener(new TraverseListener() {
			public void keyTraversed(TraverseEvent e) {
				// if ()
			}
		});
		if ((style & NOT_NULL) != 0)
			addConstraint(new SelfConstraint() {
				public boolean check() {
					if (!dirty)
						return value != null;
					return extract() != null;
				}
			}, new VerificationError(I18n.get("constraint.notnull.0")
					+ getName() + I18n.get("constraint.notnull.2")));
	}

	/**
	 * If the edit has been modified since its last change, it returns the value
	 * extracted from itself. Otherwise, it returns the value set last time.
	 * 
	 * @return the value of the edit
	 */
	public final T get() {
		if (dirty)
			return extract();
		// if (value == null)
		// if (value == null && defaultComputation != null)
		// return defaultComputation.defaultValue();
		return value;
	}

	/**
	 * Sets the actual value of the edit and fills the value into the controls
	 * of the edit. If the value is null then the edit is cleared. Finally, it
	 * fires a change event.
	 * 
	 * @param value
	 */
	public final void set(T value) {
		System.out.println("Edit.set() " + name + " " + value);
		if (isFixed())
			return;
		T oldValue = this.value;
		this.value = value;
		if (value == null)
			clear();
		else
			fill();
		markClean();
		fireChange(new ChangeEvent<Edit<T>, T>(this, oldValue, value));
	}

	/**
	 * Makes the edit read only.
	 * 
	 * @param b If true then the edit will be read only, otherwise not.
	 */
	public final void fix(boolean b) {
		fixed = b;
		setEditable(!b);
		setBackground(COLOR_FIXED);
		form.refresh();
	}

	/**
	 * Clears the controls of the edit.
	 */
	public abstract void clear();

	/**
	 * Fills the controls of the edit with its actual value.
	 */
	public abstract void fill();

	/**
	 * Extracts the value from the controls of the edit.
	 * 
	 * @return the extracted value
	 */
	public abstract T extract();

	/**
	 * Returns the name of the property handled by the edit.
	 * 
	 * @return the name of the property
	 */
	public String getPropertyName() {
		return prop;
	}

	/**
	 * Returns the name of the edit. It may appear in GUI messages.
	 * 
	 * @return the name of the edit
	 */
	public String getName() {
		return name;
	}

	// public void extract(Object from) {
	// try {
	// PropertyUtils.setProperty(from, prop, extract());
	// } catch (IllegalAccessException e) {
	// // TODO Auto-generated catch block
	// e.printStackTrace();
	// } catch (InvocationTargetException e) {
	// // TODO Auto-generated catch block
	// e.printStackTrace();
	// } catch (NoSuchMethodException e) {
	// // TODO Auto-generated catch block
	// e.printStackTrace();
	// }
	// }

	// public Color getBackground() {
	// return crl.getBackground();
	// }
	// public abstract Color getBackground();

	// public void setBackground(Color clr) {
	// crl.setBackground(clr);
	// }
	public abstract void setBackground(Color clr);

	public void resetBackground() {
		setBackground(null);
	}

	public final boolean isDirty() {
		return dirty;
	}

	public final boolean isValid() {
		if (dirty2)
			try {
				verify();
			} catch (VerificationException exc) {
			}
		return !invalid;
	}

	public void markClean() {
		dirty = false;
		if (!invalid)
			resetBackground();
	}

	public void markDirty() {
		dirty = dirty2 = true;
		// if (isValid())
		if (!invalid)
			setBackground(COLOR_DIRTY);
		fireModification(new ModificationEvent<T>(this));
	}

	public void markValid() {
		invalid = false;
		if (dirty)
			setBackground(COLOR_DIRTY);
		else
			resetBackground();
	}

	public void markInvalid() {
		invalid = true;
		setBackground(COLOR_INVALID);
	}

	public abstract boolean isEnabled();

	public abstract void setEnabled(boolean b);

	// TODO
	public boolean isEditable() {
		return !readonly;
	}

	// TODO: this should be abstract and subclasses should provide a
	// reasonable implementation.
	// public void setEditable(boolean b) {
	// throw new UnsupportedOperationException();
	// }

	public abstract void setEditable(boolean b);

	public boolean isFixed() {
		return fixed;
	}

	@Override
	protected void fireKeyPressed(KeyEvent event) {
		super.fireKeyPressed(event);
		form.fireKeyPressed(event);
	}

	@Override
	protected void fireKeyReleased(KeyEvent event) {
		super.fireKeyReleased(event);
		form.fireKeyReleased(event);
	}

	// --------------------------------------------------------------
	// Listening for modifications
	// --------------------------------------------------------------

	private class WidgetModificationListener implements ModifyListener,
			SelectionListener {
		public final void modifyText(ModifyEvent e) {
			markDirty();
		}

		public final void widgetSelected(SelectionEvent e) {
			markDirty();
		}

		public final void widgetDefaultSelected(SelectionEvent e) {
			markDirty();
		}
	}

	/**
	 * lsn_WIDGET_MOD should be added for every widget used up in the
	 * implementation of the Edit.
	 */
	protected final WidgetModificationListener lsn_WIDGET_MOD = new WidgetModificationListener();

	private List<ModificationListener> modificationListeners = new ArrayList<ModificationListener>();

	public void addModificationListener(ModificationListener listener) {
		modificationListeners.add(listener);
	}

	public void removeModificationListener(ModificationListener listener) {
		modificationListeners.remove(listener);
	}

	private final void fireModification(ModificationEvent<T> event) {
		for (ModificationListener lsn : modificationListeners)
			lsn.modification(event);
	}

	// --------------------------------------------------------------
	// Storing and verifying constraints
	// --------------------------------------------------------------

	private SelfVerifier verifier = new SelfVerifier();

	protected class EditConstraint extends SelfConstraintAdapter {
		EditConstraint(Constraint c) {
			super(c);
		}

		public boolean check() {
			return check(get());
		}
	}

	protected void addConstraint(SelfConstraint c, VerificationException e) {
		verifier.addConstraint(c, e);
	}

	protected void removeConstraint(SelfConstraint c) {
		verifier.removeConstraint(c);
	}

	public void addConstraint(Constraint c, VerificationException e) {
		addConstraint(new EditConstraint(c), e);
	}

	public void removeConstraint(Constraint c) {
		verifier.removeConstraint(c);
	}

	public void verify() throws VerificationException {
		try {
			dirty2 = false;
			verifier.verify();
			if (invalid)
				markValid();
		} catch (VerificationException exc) {
			if (!invalid)
				markInvalid();
			throw exc;
		}
	}

	// --------------------------------------------------------------
	// Default values
	// --------------------------------------------------------------

	public static interface DefaultComputation<TT> {
		TT defaultValue();
	}

	private T defaultValue;
	private DefaultComputation<T> defaultComputation;

	public boolean hasDefault() {
		return defaultValue != null || defaultComputation != null;
	}

	public T getDefault() {
		if (defaultComputation != null)
			return defaultComputation.defaultValue();
		return defaultValue;
	}

	public void setDefault() {
		set(getDefault());
	}

	public void setDefault(T defaultValue) {
		this.defaultValue = defaultValue;
		defaultComputation = null;
		if (value == null)
			set(defaultValue);
	}

	public void setDefault(DefaultComputation<T> defaultComp) {
		this.defaultComputation = defaultComp;
		defaultValue = null;
		if (value == null)
			set(defaultComp.defaultValue());
	}
}
