package semorg.gui.util;

import java.sql.Time;
import java.util.Calendar;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import 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.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Spinner;
import org.eclipse.swt.widgets.Text;

import semorg.sql.util.Utility;

/**
 * The Class TimeControl provides widgets to enter times.
 */
public class TimeControl extends Composite {

    /** The hours spinner. */
    final private Spinner hours;

    /** The minutes spinner. */
    final private Spinner minutes;

    /** The button to enable the control. */
    final private Button checker;

    /** The colon between the hours and minutes. */
    final private Label floatingPoint;

    /** The background text field. */
    private Text backgroundHack;

    /** The default font. */
    final private Font defaultFont;

    /**
     * Indicates if the composite is activated or not. This Composite delivers
     * only time values if it is activated.
     */
    private boolean activated;

    /** If <tt>true</tt> the control is enabled, <tt>false</tt> otherwise. */
    private boolean enabled;

    /** The listeners which react on changes of the time. */
    private Set<TimeChangeListener> listeners = new HashSet<TimeChangeListener>();

    /**
     * The listener interface for receiving timeChange events. The class that is
     * interested in processing a timeChange event implements this interface,
     * and the object created with that class is registered with a component
     * using the component's <code>addTimeChangeListener<code> method. When
     * the timeChange event occurs, that object's appropriate
     * method is invoked.
     * 
     */
    public interface TimeChangeListener {

	/**
	 * This method gets invoked on changes of the time.
	 */
	public void timeChanged();
    }

    /**
     * Instantiates a new time control.
     * 
     * @param par
     *                the parent composite
     */
    public TimeControl(Composite par) {

	super(par, SWT.NULL);
	this.setLayout(new FormLayout());
	defaultFont = new Font(this.getDisplay(), new FontData("Sans Serif", 8,
		SWT.NORMAL));
	;

	floatingPoint = new Label(this, SWT.NONE);
	floatingPoint.setText(":");
	floatingPoint.setBackground(par.getDisplay().getSystemColor(
		SWT.COLOR_WHITE));
	floatingPoint.setLayoutData(Utility.getFormData(0, 2, 0, 18, 0, 57, 0,
		59));
	floatingPoint.setFont(defaultFont);

	hours = new Spinner(this, SWT.NONE | SWT.RIGHT_TO_LEFT);
	hours.setMinimum(0);
	hours.setMaximum(23);
	hours.setFont(defaultFont);
	hours.setLayoutData(Utility.getFormData(0, 2, 0, 18, 0, 21, 0, 57));
	hours.addModifyListener(new ModifyListener() {

	    public void modifyText(ModifyEvent arg0) {
		fireInputChanged();
	    }
	});

	minutes = new Spinner(this, SWT.NONE);
	minutes.setMinimum(0);
	minutes.setMaximum(59);
	minutes.setFont(defaultFont);
	minutes.setLayoutData(Utility.getFormData(0, 2, 0, 18, 0, 59, 0, 95));
	minutes.addModifyListener(new ModifyListener() {

	    public void modifyText(ModifyEvent arg0) {
		fireInputChanged();
	    }
	});

	checker = new Button(this, SWT.CHECK);
	checker.setLayoutData(Utility.getFormData(0, 4, 0, 16, 0, 6, 0, 18));
	checker.addSelectionListener(new SelectionListener() {

	    public void widgetSelected(SelectionEvent event) {
		setActivated(checker.getSelection());
		fireInputChanged();
	    }

	    public void widgetDefaultSelected(SelectionEvent event) {
		widgetSelected(event);
	    }
	});

	backgroundHack = new Text(this, SWT.BORDER | SWT.READ_ONLY);
	backgroundHack.setEnabled(false);
	backgroundHack.setBackground(par.getDisplay().getSystemColor(
		SWT.COLOR_WHITE));
	backgroundHack.setLayoutData(Utility.getFormData(0, 0, 0, 20, 0, 0, 0,
		97));

	setActivated(false);

    }

    /**
     * Returns the hours of the {@link #hours} control.
     * 
     * @return the hours as int or -1 if activated is <tt>null</tt>.
     */
    public int getHours() {
	if (activated)
	    return hours.getSelection();
	else
	    return -1;
    }

    /**
     * Returns the minutes of the {@link #minutes} control.
     * 
     * @return the minutes as int or -1 if activated is <tt>null</tt>.
     */
    public int getMinutes() {
	if (activated)
	    return minutes.getSelection();
	else
	    return -1;
    }

    /**
     * If acivated is <tt>true</tt> this method returns the time entered in
     * the {@link #hours} and {@link #minutes} controls.
     * 
     * @return the time from the controls.
     */
    public Time getTime() {
	if (activated) {
	    Calendar calendar = Calendar.getInstance();
	    calendar.set(Calendar.HOUR_OF_DAY, hours.getSelection());
	    calendar.set(Calendar.MINUTE, minutes.getSelection());
	    calendar.set(Calendar.SECOND, 0);
	    calendar.set(Calendar.MILLISECOND, 0);
	    return new Time(calendar.getTimeInMillis());
	} else
	    return null;
    }

    /**
     * Fires all {@link TimeChangeListener} listener in the vector
     * {@link #listeners}.
     */
    private void fireInputChanged() {
	Iterator<TimeChangeListener> iter = listeners.iterator();
	while (iter.hasNext()) {
	    TimeChangeListener listener = iter.next();
	    listener.timeChanged();
	}
    }

    /**
     * Adds the time change listener.
     * 
     * @param listener
     *                the listener to add
     */
    public void addTimeChangeListener(TimeChangeListener listener) {
	listeners.add(listener);
    }

    /**
     * Removes the time change listener.
     * 
     * @param listener
     *                the listener to remove
     */
    public void removeTimeChangeListener(TimeChangeListener listener) {
	listeners.remove(listener);
    }

    /**
     * Sets time the given time to {@link #hours} and {@link #minutes} controls.
     * If the given time is <tt>null</tt> the controls get deactivated.
     * 
     * @param time
     *                the new time
     */
    public void setTime(Time time) {
	setActivated(time != null);
	if (time != null) {
	    Calendar calendar = Calendar.getInstance();
	    calendar.setTime(time);
	    hours.setSelection(calendar.get(Calendar.HOUR_OF_DAY));
	    minutes.setSelection(calendar.get(Calendar.MINUTE));
	}
    }

    /**
     * Activates / deactivates the controls of this composite depending on the
     * given parameter.
     * 
     * @param activ
     *                the new activated state
     */
    protected void setActivated(boolean activ) {
	activated = activ;
	checker.setSelection(activated);
	hours.setEnabled(enabled && activated);
	minutes.setEnabled(enabled && activated);

	if (activated) {
	    hours.setForeground(getDisplay().getSystemColor(SWT.COLOR_BLACK));
	    minutes.setForeground(getDisplay().getSystemColor(SWT.COLOR_BLACK));
	} else {
	    hours.setForeground(getDisplay()
		    .getSystemColor(SWT.COLOR_DARK_GRAY));
	    minutes.setForeground(getDisplay().getSystemColor(
		    SWT.COLOR_DARK_GRAY));
	}
    }

    /**
     * Enables the receiver if the argument is true, and disables it otherwise.
     * A disabled control is typically not selectable from the user interface
     * and draws with an inactive or "grayed" look.
     * 
     * @param enabled
     *                the new enabled state.
     */
    public void setEnabled(boolean enabled) {
	this.enabled = enabled;
	hours.setEnabled(enabled && activated);
	minutes.setEnabled(enabled && activated);
	checker.setEnabled(enabled);
	floatingPoint.setEnabled(enabled);
    }

    /**
     * Sets a pair of key and value to this control needed for testing with
     * ATOSj.
     * 
     * @param key
     *                the key (type) of the value set
     * @param value
     *                the value set to the control
     */
    public void setData(String key, String value) {
	super.setData(key, value);
	if (key.equals(Utility.ATOSJ_COMPONENT_NAME_KEY)) {
	    hours.setData(key, value + "_hours");
	    minutes.setData(key, value + "_minutes");
	    checker.setData(key, value + "_checker");
	}
    }

    /**
     * Sets the tooltip text of the composite to the tooltips of the
     * {@link #checker}, the {@link #floatingPoint}, the {@link #hours} and
     * the {@link #minutes} components, such that it appears if the user moves
     * the mouse pointer of one of the visible components of the control.
     */
    public void setToolTipText(String toolTipText) {
	this.checker.setToolTipText(toolTipText);
	this.floatingPoint.setToolTipText(toolTipText);
	this.hours.setToolTipText(toolTipText);
	this.minutes.setToolTipText(toolTipText);
    }

}