package semorg.sql.util;

import java.io.InputStream;
import java.lang.reflect.Array;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;

import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.TabFolder;
import org.eclipse.swt.widgets.TabItem;
import org.eclipse.swt.widgets.Text;

/**
 * Utility-Class which encapsulates some useful methods for the database and GUi
 * layer.
 */
public class Utility {

    /**
     * Needed for regression tests with ATOSj. (for identifying the GUI
     * elements).
     */
    public static final String ATOSJ_COMPONENT_NAME_KEY = "ATOSJ_COMPONENT_NAME_KEY";

    /** A registry containing all {@link Image} objects of the application. */
    private static ImageRegistry imgReg;

    /**
     * {@link Color} instance for red-coloring of text fields (unfilled
     * mandatory fields).
     */
    public static final Color LIGHT_YELLOW = new Color(Display.getDefault(),
	    255, 255, 153);
    /**
     * {@link Color} instance for yellow-coloring of text fields (mandatory
     * fields).
     */
    public static final Color LIGHT_RED = new Color(Display.getDefault(), 255,
	    192, 192);

    /** Date formatter using standard localization. */
    public static final DateFormat dateOnlyFormatter = DateFormat
	    .getDateInstance();
    /** Date and time formatter using standard localization. */
    public static final DateFormat dateAndTimeFormatter = DateFormat
	    .getDateTimeInstance();
    /** Time formatter using the format <code>HH:mm</code>. */
    public static final DateFormat timeOnlyFormatter = new SimpleDateFormat(
	    "HH:mm");

    /** Parses strings for floating-point numbers. */
    private static NumberFormat floatParser = NumberFormat.getNumberInstance();

    /**
     * Colors empty mandatory fields with red color if they loose the focus. The
     * field get colored yellow if they regain the focus.<br>
     * <br>
     * 
     * Thus this listener marks input errors in the GUI.
     */
    public static FocusListener checkEmptyListener = new FocusListener() {

	public void focusGained(FocusEvent e) {
	    if (e.getSource() instanceof Text) {
		Text t = (Text) e.getSource();
		t.setBackground(LIGHT_YELLOW);
	    } else if (e.getSource() instanceof Combo) {
		Combo c = (Combo) e.getSource();
		c.setBackground(LIGHT_YELLOW);
	    }

	}

	public void focusLost(FocusEvent e) {
	    if (e.getSource() instanceof Text) {
		Text t = (Text) e.getSource();
		if (t.getText().trim().length() == 0)
		    t.setBackground(LIGHT_RED);
	    } else if (e.getSource() instanceof Combo) {
		Combo c = (Combo) e.getSource();
		if (c.getText().trim().length() == 0)
		    c.setBackground(LIGHT_RED);
	    }

	}
    };

    /**
     * Checks if the entered String is a valid integer. If not it colors the
     * text field red. Otherwise the field gets the color white.
     */
    public static FocusListener checkCorrectIntListener = new FocusListener() {

	public void focusGained(FocusEvent e) {
	    if (e.getSource() instanceof Text) {
		Text t = (Text) e.getSource();
		t.setBackground(Display.getDefault().getSystemColor(
			SWT.COLOR_WHITE));
	    }
	}

	public void focusLost(FocusEvent e) {
	    if (e.getSource() instanceof Text) {
		Text t = (Text) e.getSource();
		String text = t.getText();
		if (!isCorrectInteger(text))
		    t.setBackground(LIGHT_RED);
	    }
	}
    };

    /**
     * Checks if the entered String is a valid float number. If not it colors
     * the text field red. Otherwise the field gets the color white.
     */
    public static FocusListener checkCorrectFloatListener = new FocusListener() {

	public void focusGained(FocusEvent e) {
	    if (e.getSource() instanceof Text) {
		Text t = (Text) e.getSource();
		t.setBackground(Display.getDefault().getSystemColor(
			SWT.COLOR_WHITE));
	    }
	}

	public void focusLost(FocusEvent e) {
	    if (e.getSource() instanceof Text) {
		Text t = (Text) e.getSource();
		String text = t.getText();
		if (!isCorrectFloat(text))
		    t.setBackground(LIGHT_RED);
	    }
	}
    };

    /**
     * Creates for a SWT control instance a {@link FormData} instance and
     * returns it.<br>
     * <br>
     * 
     * The {@link FormData} object specifies how the {@link FormLayout} manager
     * should adjust the widget.The {@link FormData} instance itselfs is
     * specified by {@link FormAttachment}s, which attach the four borders
     * (top, bottom, left, right) of a widget to several positions of shell.<br>
     * <br>
     * 
     * <i>Hint:</i>
     * <ul>
     * <li> If you use the {@link FormLayout}, you MUST assign to every widget
     * a OWN {@link FormData}. </li>
     * </ul>
     * 
     * @param topPercentage
     *                Position used to attach the top border of a widget.
     * @param topOffset
     *                Offset to the position of the top border.
     * @param bottomPercentage
     *                Position used to attach the bottom border of a widget.
     * @param bottomOffset
     *                Offset to the position of the bottom border.
     * @param leftPercentage
     *                Position used to attach the left border of a widget.
     * @param leftOffset
     *                Offset to the position of the left border.
     * @param rightPercentage
     *                Position used to attach the right border of a widget.
     * @param rightOffset
     *                Offset to the position of the left border.
     * @return {@link FormData} object created with the given parameters.
     */
    public static FormData getFormData(Object topPercentage, int topOffset,
	    Object bottomPercentage, int bottomOffset, Object leftPercentage,
	    int leftOffset, Object rightPercentage, int rightOffset) {
	FormData fm = new FormData();

	if (topPercentage instanceof Control)
	    fm.top = new FormAttachment((Control) topPercentage, topOffset);
	else if (topPercentage instanceof Integer
		&& isvalidPercentage((Integer) topPercentage))
	    fm.top = new FormAttachment((Integer) topPercentage, topOffset);

	if (bottomPercentage instanceof Control)
	    fm.bottom = new FormAttachment((Control) bottomPercentage,
		    bottomOffset);
	else if (bottomPercentage instanceof Integer
		&& isvalidPercentage((Integer) bottomPercentage))
	    fm.bottom = new FormAttachment((Integer) bottomPercentage,
		    bottomOffset);

	if (leftPercentage instanceof Control)
	    fm.left = new FormAttachment((Control) leftPercentage, leftOffset);
	else if (leftPercentage instanceof Integer
		&& isvalidPercentage((Integer) leftPercentage))
	    fm.left = new FormAttachment((Integer) leftPercentage, leftOffset);

	if (rightPercentage instanceof Control)
	    fm.right = new FormAttachment((Control) rightPercentage,
		    rightOffset);
	else if (rightPercentage instanceof Integer
		&& isvalidPercentage((Integer) rightPercentage))
	    fm.right = new FormAttachment((Integer) rightPercentage,
		    rightOffset);

	return fm;
    }

    /**
     * Checks if the given percentage is valid.
     * 
     * @param val
     *                value to check.
     * @return <tt>true</tt> if the given value is a valid percentage,
     *         <tt>false</tt> otherwise.
     */
    private static boolean isvalidPercentage(int val) {
	return ((val >= 0) && (val <= 100));
    }

    /**
     * Creates a tab whose contents is a {@link Label} and a {@link Text} field.
     * 
     * @param parent
     *                TabFolder, which contains the tab.
     * @param name
     *                name of the tab.
     * @param font
     *                Desired font. <code>null</code>, if using the default
     *                font.
     * @param limit
     *                size of the {@link Text} field.
     * @return created {@link Text} field.
     */
    public static Text createTextTab(TabFolder parent, String name, Font font,
	    int limit) {
	TabItem textTab = new TabItem(parent, SWT.NULL);
	textTab.setText(name);

	Composite tabContent = new Composite(parent, SWT.NONE);
	textTab.setControl(tabContent);
	tabContent.setLayout(new FormLayout());

	Label header = new Label(tabContent, SWT.READ_ONLY);
	header.setText(name);
	header.setLayoutData(getFormData(14, 0, null, 0, 0, 9, null, 0));

	Text text = new Text(tabContent, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL
		| SWT.WRAP);
	text.setTextLimit(limit);
	text.setLayoutData(getFormData(0, 39, 100, -16, 0, 9, 100, -10));

	if (font != null) {
	    header.setFont(font);
	    text.setFont(font);
	}
	return text;
    }

    /** Converts a JAVA date into SQL date. */
    public static java.sql.Date convertToSQLDate(java.util.Date date) {
	return new java.sql.Date(date.getTime());
    }

    /** Converts a JAVA timestamp inti a SQL timestamp. */
    public static java.sql.Timestamp convertToTimestamp(java.util.Date date) {
	return new java.sql.Timestamp(date.getTime());
    }

    /**
     * Sets the number of characters a {@link Text} field can take as well as
     * the width of this {@link Text} field.
     * 
     * @param comp
     *                {@link Text} field, whose length should get modified.
     * @param size
     *                Number of characters which can be entered. The width is
     *                set to <code>6*size</code>.
     */
    public static void setLength(Text comp, int size) {
	GridData gd; // um die Pixel-Breite des Textfelds festzulegen
	Object ld = comp.getLayoutData();
	if (ld != null && ld instanceof GridData)
	    gd = (GridData) ld;
	else
	    gd = new GridData();

	comp.setTextLimit(size);
	gd.widthHint = 6 * size;
	comp.setLayoutData(gd);
    }

    /**
     * Sets the number of characters a {@link Text} field can take as well as
     * the number of characters shown.
     * 
     * @param comp
     *                {@link Text} field, whose length should get modified.
     * @param textLength
     *                Number of characters which can be entered.
     * @param lettersShown
     *                Number of characters shown.
     */
    public static void setLength(Text comp, int textLength, int lettersShown) {
	GridData gd;
	Object ld = comp.getLayoutData();
	if (ld != null && ld instanceof GridData)
	    gd = (GridData) ld;
	else
	    gd = new GridData();

	comp.setTextLimit(textLength);
	gd.widthHint = 6 * lettersShown;
	comp.setLayoutData(gd);
    }

    /**
     * Sets the number of characters a {@link Combo} field can take as well as
     * the width of this {@link Combo} field.
     * 
     * @param comp
     *                {@link Combo} field, whose length should get modified.
     * @param size
     *                Number of characters which can be entered. The width is
     *                set to <code>6*size</code>.
     */
    public static void setLength(Combo comp, int size) {
	GridData gd;
	Object ld = comp.getLayoutData();
	if (ld != null && ld instanceof GridData)
	    gd = (GridData) ld;
	else
	    gd = new GridData();

	comp.setTextLimit(size);
	gd.widthHint = 6 * size;
	comp.setLayoutData(gd);
    }

    /**
     * Aligns a {@link Label} right.
     * 
     * @param label
     *                the label to align.
     */
    public static void alignRight(Control label) {
	GridData gd;
	Object ld = label.getLayoutData();
	if (ld != null && ld instanceof GridData)
	    gd = (GridData) ld;
	else
	    gd = new GridData();
	gd.horizontalAlignment = GridData.END;
	label.setLayoutData(gd);
    }

    /**
     * Loads a image into a {@link ImageRegistry}.
     * 
     * @param name
     *                (file-)name of the image to add.
     */
    private static void initImage(String name) {
	InputStream stream = Utility.class.getClassLoader()
		.getResourceAsStream("semorg/gui/icon/" + name);
	imgReg.put(name, new Image(Display.getDefault(), stream));
    }

    /**
     * Returns for a given image name a {@link Image} object from the
     * {@link ImageRegistry} {@link #imgReg}. If the image is not already
     * present in the registry it gets added.
     * 
     * @param name
     *                (file-)name of the image.
     */
    public static Image getImage(String name) {
	if (imgReg == null)
	    imgReg = new ImageRegistry(Display.getDefault());

	Image img = imgReg.get(name);
	if (img == null) {
	    initImage(name);
	    return imgReg.get(name);
	} else {
	    return img;
	}
    }

    /**
     * Disposes all images by disposing the {@link ImageRegistry}.
     */
    public static void disposeImages() {
	imgReg.dispose();
    }

    /**
     * Parses a float-number from a given string.
     * 
     * @return {@link Float} object, if the parsing was successful otherwise
     *         <code>null</code>.
     */
    public static Float parseFloat(String floatString) {
	if (floatString == null || floatString.trim().length() == 0)
	    return null;
	else
	    try {
		return new Float(floatParser.parse(floatString).floatValue());
	    } catch (Exception e) {
		return null;
	    }
    }

    /**
     * Checks whether a given string is a valid float number.
     * 
     * @return If <code>null</code>, only whitespaces, a empty String or a
     *         valid float number was given the method returns <code>true</code>,
     *         otherwise <code>false</code>.
     */
    public static boolean isCorrectFloat(String floatString) {
	if (floatString == null || floatString.trim().length() == 0)
	    return true;
	else {
	    floatString = floatString.trim();
	    // Index, bei dem ein Parse-Fehler auftrat; bei einer korrekten
	    // Float-Zahl also letzter Index + 1
	    ParsePosition pp = new ParsePosition(0);
	    floatParser.parse(floatString, pp);
	    return pp.getIndex() == floatString.length();
	}
    }

    /**
     * Parses a integer from a given string.
     * 
     * @return {@link Integer} object, if the parsing was successful otherwise
     *         <code>null</code>.
     */
    public static Integer parseInteger(String integerString) {
	if (integerString == null || integerString.trim().length() == 0)
	    return null;
	else
	    try {
		return new Integer(Integer.parseInt(integerString));
	    } catch (Exception e) {
		return null;
	    }
    }

    /**
     * Checks whether a given string is a valid integer.
     * 
     * @return If <code>null</code>, only whitespaces, a empty String or a
     *         valid integer was given the method returns <code>true</code>,
     *         otherwise <code>false</code>.
     */
    public static boolean isCorrectInteger(String integerString) {
	if (integerString == null || integerString.trim().length() == 0)
	    return true;
	else
	    try {
		Integer.parseInt(integerString);
		return true;
	    } catch (Exception e) {
		return false;
	    }
    }

    /**
     * Creates a string from a given {@link Float} object.
     * 
     * @return Empty string, if <code>null</code> was given, otherwise the
     *         created string.
     */
    public static String createFloatText(Float theFloat) {
	if (theFloat == null)
	    return "";

	return floatParser.format(theFloat);

	/*
	 * int val = (int)theFloat.floatValue() * 100; String returnValue =
	 * ""+val / 100; val %= 100; returnValue+= "."+ val/10; val %= 10;
	 * returnValue+= val/10; return returnValue;
	 */
    }

    /**
     * Sets several properties for the FloatFormater/FloatParser:
     * <ul>
     * <li>Maximal and minimal number of fraction digits.</li>
     * <li>No grouping of numbers, e.g. grouping: 1223786 -> 1.234.786</li>
     * </ul>
     */
    public static void initUtilities() {
	floatParser.setMaximumFractionDigits(2);
	floatParser.setMinimumFractionDigits(2);
	floatParser.setGroupingUsed(false);
    }

    /**
     * Does a type cast of all elements of a given array.
     * 
     * @param array
     *                Array, whose elements get type casted.
     * @param clazz
     *                Desired class to cast the elements to.
     * @return Array with type casted elements.
     */
    public static Object[] castArray(Object[] array, Class clazz) {

	Object[] returnArray = (Object[]) Array
		.newInstance(clazz, array.length);

	for (int i = 0; i < array.length; i++)
	    returnArray[i] = (array[i]);

	return returnArray;
    }
}
