/**
 * 
 */
package es.dalocar.facturamas.swt;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;

import org.apache.log4j.Logger;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.Button;
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.MessageBox;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Spinner;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;

//import com.cloudgarden.resource.SWTResourceManager;
import com.gface.date.DatePickerCombo;
import com.gface.date.DatePickerStyle;

import es.dalocar.software.Messages;
import es.dalocar.software.utils.ColorDefinition;
import es.dalocar.software.utils.ResourceUtils;



/**
 * Utilidades para manejo de widgets SWT
 * 
 * @author dlopez
 *
 */
public class SWTUtils {
	/**
	 * Logger for this class
	 */
	private static final Logger log = Logger.getLogger(SWTUtils.class);
	private static final boolean DEBUG = log.isDebugEnabled();
	
	public static final String CONTROL_REQUIRED = "control.required";
	public static final String CONTROL_NAME = "control.name";

	/**
	 * Private constructor
	 */
	private SWTUtils() {
		// Do nothing
	}
	
	/**
	 * Finds all the Controls whose class is <code>clazz</code> or an instance of it
	 * 
	 * @param composite parent Composite
	 * @param clazz The class to be searched
	 * @return the populated Collection, which may be empty
	 */
	public static Collection findControls(Composite composite, Class clazz) {
		
		Collection coll = new ArrayList();
		
		if (Control.class.isAssignableFrom(clazz)) {
			Control[] controls = composite.getChildren();
			for (int i = 0; i < controls.length; i++) {
				Control control = controls[i];
				if (clazz.isInstance(control)) {
					coll.add(control);
				}

				if (control instanceof Composite) {
					Composite nested = (Composite) control;
					Collection nestedColl = findControls(nested, clazz);
					coll.addAll(nestedColl);
				}
			}
		}
		
		return coll;
	}
	
	/**
	 * Find a control for the specified name
	 * @param composite
	 * @param name
	 * @return the Control
	 */
	public static Control findControlByName(Composite composite, String name) {

		Control found = null;
		
		Control[] controls = composite.getChildren();
		for (int i = 0; i < controls.length; i++) {
			Control control = controls[i];
			String controlName = (String) control.getData(CONTROL_NAME);
			if (name.equals(controlName)) {
				found = control;
				break;
			}

			//Si es un composite, buscar en él
			if (control instanceof Composite) {
				Composite nested = (Composite) control;
				Control nestedCtrl = findControlByName(nested, name);
				if (nestedCtrl != null) {
					found = nestedCtrl;
					break;
				}
			}
		}
		
		return found;
	}
	
	
	/**
	 * Select value from combo. The specified property is used
	 * to set the Combo text values.
	 * @param combo Combo to be refreshed
	 * @param data object to select 	 
	 */
	public static void selectCombo(Combo combo, Object data) {
		
		List l=(List)combo.getData();
				
		
		int index=l.indexOf(data);
				
		
		if(index!=-1){
			combo.select(index);
		}		
	}
	
	/**
	 * Refreshes a Combo Control with the specified List. The specified property is used
	 * to set the Combo text values.
	 * @param combo Combo to be refreshed
	 * @param data a List containing the data 
	 * @param property property to use when filling the Combo text list
	 */
	public static void refreshCombo(Combo combo, List data, String property) {
		combo.removeAll();
		combo.setData(data);
		
		Iterator it = data.iterator();
		while (it.hasNext()) {
			Object item = it.next();
			Object value = getPropertyValue(item, property);
			combo.add(String.valueOf(value));
		}			
	}
	
	/**
	 * Refreshes a Combo Control with the specified List. The specified properties are used
	 * to set the Combo text values. The first of the properties is used as value
	 * @param combo Combo to be refreshed
	 * @param data a List containing the data 
	 * @param properties properties list to use when filling the Combo text list
	 */
	public static void refreshCombo(Combo combo, List data, String [] properties) {
		combo.removeAll();
		combo.setData(data);
		
		Iterator it = data.iterator();
		while (it.hasNext()) {
			Object item = it.next();
			Object value = getPropertyValue(item, properties[0]);
			String valueToAdd ="";
			for(int i=0;i < properties.length;i++) {
				if(i>0)
					valueToAdd+=" - ";
				valueToAdd += String.valueOf(getPropertyValue(item,properties[i]));
			}
				combo.add(String.valueOf(valueToAdd));
		}			
	}
	
	/**
	 * Refreshes a Text control with the specified valua. Checks null values
	 * @param text the Text control
	 * @param str the String value
	 */
	public static void refreshText(Text text, String str) {
		if (str == null) text.setText("");
		else text.setText(str);
	}

	/**
	 * Refreshes a Text control with the specified valua. Checks null values
	 * @param text the Text control
	 * @param integer the Integer value
	 */
	public static void refreshText(Text text, Integer integer) {
		if (integer == null) text.setText("");
		else text.setText(integer.toString());
	}
	
	/**
	 * Refreshes a Label control with the specified value. Checks null values
	 * @param label the Label control
	 * @param str the String value
	 */
	public static void refreshLabel(Label label, String str) {
		if (str == null) label.setText("");
		else label.setText(str);
	}
	
	/**
	 * Refreshes a Label control with the specified value. Checks null values
	 * @param label the Label control
	 * @param integer the Integer value
	 */
	public static void refreshLabel(Label label, Integer integer) {
		if (integer == null) label.setText("");
		else label.setText(integer.toString());
	}

	
	/**
	 * Obtains the corresponding data to the selected combo index. The data is required
	 * to be a List
	 * @param combo the combo to populate
	 * @return the corresponding Object in the List, following the selected index
	 */
	public static Object populateCombo(Combo combo) {
		Object value = null;
		List data = null;
		
		if (combo.getSelectionIndex() < 0) {
			value = null;
		} else if (combo.getData() != null) {
			data = (List) combo.getData();
			value = data.get(combo.getSelectionIndex());
		}
		return value;
	}
	
	/**
	 * Obtains the property value from an object
	 * @param item the object
	 * @param property the property name to extract
	 * @return the property value or an empty string if not found
	 */
	public static Object getPropertyValue(Object item, String property) {
		Object ret = null;
		String prefix = "get";
		String firstLetter = property.substring(0,1).toUpperCase();
		String remaining = property.substring(1);
		String methodName = prefix.concat(firstLetter).concat(remaining);
		try {
			Method method = item.getClass().getMethod(methodName, new Class[] {});
			ret = method.invoke(item, new Object[] {});
		} catch (SecurityException e) {
			throw new RuntimeException(e);
		} catch (NoSuchMethodException e) {
			//Si no existe ese getter, devolvemos cadena vacía
			ret = "";
		} catch (IllegalArgumentException e) {
			throw new RuntimeException(e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException(e);
		} catch (InvocationTargetException e) {
			throw new RuntimeException(e);
		}
		
		return ret;
	}

	/**
	 * Carga en el combo que le pasamos como parametro las temporadas
	 * a partir del rango dado en el fichero de recursos
	 * @param Combo Box que queremos rellenar	 
	 */
	public static void loadTemporadas(Combo c) {
		int rango=ResourceUtils.getInt(ResourceUtils.YEAR_RANGE);
		int limite=ResourceUtils.getInt(ResourceUtils.MONTH_CHANGE);
		Calendar g=GregorianCalendar.getInstance();
		int año=g.get(Calendar.YEAR);	
		int mes=g.get(Calendar.MONTH);
		if(mes < limite)
			año--;
		int añoINI=año-rango;
		int añoFIN=año+rango;
		
		c.removeAll();
		
		
		//TODO: (dlopez) Refactorizar Strings
		StringBuffer  sb =  null;
		for(int i=añoINI;i<=añoFIN;i++){
			sb = new StringBuffer();
			sb.append(String.valueOf(i));
			sb.append("-");
			sb.append(String.valueOf(i+1));
			c.add(sb.toString());
			c.setData(sb.toString(),String.valueOf(i));
		}
		
		c.setText(año+"-"+(año+1));
	}
			
	
	/**
	 * Convertir de String a Long
	 * @param str
	 * @return el valor correspondiente o un Long de <code>cero</code> si la
	 * cadena no es válida
	 */
	public static Long str2long(String str) {
		Long long1 = null;
		if (str != null) str = str.trim();
		try {
			long1 = Long.valueOf(str);
		} catch (NumberFormatException e) {
			long1 = Long.valueOf("0");
		}
		return long1;
	}

	/**
	 * Convertir de String a Integer
	 * @param str
	 * @return el valor correspondiente o un Integer de <code>cero</code> si la
	 * cadena no es válida
	 */
	public static Integer str2int(String str) {
		Integer int1 = null;
		if (str != null) str = str.trim();
		try {
			int1 = Integer.valueOf(str);
		} catch (NumberFormatException e) {
			int1 = Integer.valueOf("0");
		}
		return int1;
	}

	
	/**
	 * Mostrar los mensajes de validacion
	 * @param shell el objeto Shell
	 * @param validationMsg la cadena con los mensajes de validacion
	 */
	public static void showValidatonErrors(Shell shell, String validationMsg) {
		MessageBox m = new MessageBox(shell,SWT.ICON_WARNING);
		m.setText(Messages.getString("Validator.msgbox.title")); //$NON-NLS-1$
		m.setMessage(validationMsg);
		m.open();
	}

	/**
	 * Crea una clave de tipo cadena con los valores actuales del composite
	 * @param composite el composite del cual calcular su estado
	 * @return una cadena que representa el estado del Composite
	 */
	public static String calculateState(Composite composite) {

		StringBuffer sb = new StringBuffer();
		Collection controls = null;
		Iterator it = null;
		
		//Controles de texto
		controls = findControls(composite, Text.class);
		it = controls.iterator();
		while (it.hasNext()) {
			Text item = (Text) it.next();
			sb.append(addCalculation(item.getText()));
		}
		
		//Controles de DatePicker
		controls = findControls(composite, DatePickerCombo.class);
		it = controls.iterator();
		while (it.hasNext()) {
			DatePickerCombo item = (DatePickerCombo) it.next();
			sb.append(addCalculation(item.getText()));
		}
		
		//Controles de combo
		controls = findControls(composite, Combo.class);
		it = controls.iterator();
		while (it.hasNext()) {
			Combo item = (Combo) it.next();
			sb.append(addCalculation(item.getText()));
		}
		
		//Controles tipo Spinner
		controls = findControls(composite, Spinner.class);
		it = controls.iterator();
		while (it.hasNext()) {
			Spinner item = (Spinner) it.next();
			sb.append(addCalculation(item.getData().toString()));
		}
		
		//Controles de tabla
		controls = findControls(composite, Table.class);
		
		if(DEBUG){
			log.debug("Encontrados tablas: " + controls.size());
		}
		
		it = controls.iterator();
		while (it.hasNext()) {
			Table item = (Table) it.next();
			int columnas=item.getColumnCount();
			TableItem[] tableItems = item.getItems();
			for (int i = 0; i < tableItems.length; i++) {
				StringBuffer fila = new StringBuffer();
				for(int j=0;j<columnas;j++){										
					fila.append(addCalculation(tableItems[i].getText(j)));
				}
				sb.append(addCalculation(fila.toString()));				
			}
		}
		
		//Controles de boton
		controls = findControls(composite, Button.class);
		it = controls.iterator();
		while (it.hasNext()) {
			Button button = (Button) it.next();
			int style = button.getStyle();
			if ((style & SWT.CHECK) > 0 || 
					(style & SWT.RADIO) > 0 ||
					(style & SWT.TOGGLE) > 0) {
				sb.append(button.getSelection());
			}
		}		
		
		//Return
		String ret = sb.toString();
		if (DEBUG) log.debug("Calculate state for ".concat(composite.getClass().getName())
				.concat(": ").concat(ret));
		return ret;
		
	}
	
	/**
	 * Adds a calculation, i.e. a separator
	 * @param str
	 * @return the calculation
	 */
	private static String addCalculation(String str) {
		return str.concat("|");
	}
	
	/**
	 * Personalizar un Panel o Composite. Establece el fondo de los controles si
	 * son requeridos.
	 * 
	 * @param composite El composite a personalizar
	 */
	public static void customize(Composite composite) {
		
		//Buscar los controles existentes
		Collection controls = findControls(composite, Text.class);
		controls.addAll(findControls(composite, DatePickerCombo.class));
		controls.addAll(findControls(composite, Combo.class));
		controls.addAll(findControls(composite, Button.class));

		//Obtener color de fondo para los controles
		ColorDefinition cDefinition = ResourceUtils.getColorDefinition(
				ResourceUtils.WIDGET_REQUIRED_BACKGROUND);
		
		Color bgColor = SWTResourceManager.getColor(
				cDefinition.getRed(), cDefinition.getGreen(), cDefinition.getBlue());
		
		//Cambiar el color de fondo de los controles, si son campos requeridos
		Iterator it = controls.iterator();
		while (it.hasNext()) {
			Control item = (Control) it.next();
			if (item.getData(CONTROL_REQUIRED) != null
					&& item.isEnabled()) {
				item.setBackground(bgColor);
			}
		}
	}
 
	/**
	 * Filtra espacios en el texto indicado
	 * 
	 * @param text el texto
	 * @return <code>true</code> si el texto es válido, <code>false</code> en
	 * caso contrario
	 */
	public static boolean filterNoSpace(String text) {
		return (text.indexOf(" ") < 0);
	}
	
	/**
	 * Filtra datos enteros en el texto indicado
	 * @param text el texto
	 * @return <code>true</code> si el texto es válido, <code>false</code> en
	 * caso contrario
	 */
	public static boolean filterInteger(String text) {
		final String DIGITS = "0123456789"; 
		return filterChars(text, DIGITS);
	}
	
	
	/**
	 * Filtra datos numéricos (admite decimales) en el texto indicado
	 * @param text el texto
	 * @return <code>true</code> si el texto es válido, <code>false</code> en
	 * caso contrario
	 */
	public static boolean filterNumeric(String text) {
		final String DIGITS = "0123456789.,"; 
		return filterChars(text, DIGITS);
	}
	
	/**
	 * Filtra datos alfabéticos en el texto indicado
	 * @param text el texto
	 * @return <code>true</code> si el texto es válido, <code>false</code> en
	 * caso contrario
	 */
	public static boolean filterAlphabetic(String text) {
		final String DIGITS = "abcdefghijklmnñopqrstuvwxyzABCDEFGHIJKLMNÑOPQRSTUVWXYZ"; 
		return filterChars(text, DIGITS);
	}
	
	/**
	 * Filtra datos alfabéticos en minuscula en el texto indicado
	 * @param text el texto
	 * @return <code>true</code> si el texto es válido, <code>false</code> en
	 * caso contrario
	 */
	public static boolean filterLowCase(String text) {
		final String DIGITS = "abcdefghijklmnñopqrstuvwxyz"; 
		return filterChars(text, DIGITS);
	}
	
	/**
	 * Filtra datos alfabéticos en mayuscula en el texto indicado
	 * @param text el texto
	 * @return <code>true</code> si el texto es válido, <code>false</code> en
	 * caso contrario
	 */
	public static boolean filterUpperCase(String text) {
		final String DIGITS = "ABCDEFGHIJKLMNÑOPQRSTUVWXYZ"; 
		return filterChars(text, DIGITS);
	}
	
	/**
	 * Filtra datos alfanuméricos en el texto indicado
	 * @param text el texto
	 * @return <code>true</code> si el texto es válido, <code>false</code> en
	 * caso contrario
	 */
	public static boolean filterAlphaNumeric(String text) { 
		return SWTUtils.filterAlphabetic(text) || SWTUtils.filterInteger(text);
	}
	
	/**
	 * Filtra los caracteres indicados en el texto especificado
	 * @param text el texto a filtrar
	 * @param allowed cadena con los caracteres permitidos
	 * @return <code>true</code> si el texto es válido, <code>false</code> en
	 * caso contrario
	 */
	public static boolean filterChars(String text, String allowed) {
		boolean result = true;
		for (int i = 0; i < text.length(); i++) {
			char ch = text.charAt(i);
			if (allowed.indexOf(ch) < 0) {
				result = false;
				break;
			}
		}
		return result;
	}		
	
	/**
	 * Abre un Shell centrado en pantalla
	 * @param shell
	 */
	public static void openCentered(Shell shell) {
		
		final Display display = Display.getCurrent();
		int width = display.getClientArea().width;
		int height = display.getClientArea().height;
		shell.setLocation((width - shell.getSize().x) / 2, (height - shell.getSize().y) / 2);
		shell.setMaximized(false);
		shell.open();
		while (!shell.isDisposed()) {
			BusyIndicator.showWhile(display, new Runnable() {

				public void run() {
					boolean doit = false;
					do {
						doit = display.readAndDispatch();
					} while (doit);
				}
			});
			display.sleep();
		}

	}
	/**
	 * Abre un Shell centrado en pantalla
	 * @param shell
	 */
	public static void openCentered(Shell shell,boolean maximized) {
		
		final Display display = Display.getCurrent();
		int width = display.getClientArea().width;
		int height = display.getClientArea().height;
		shell.setLocation((width - shell.getSize().x) / 2, (height - shell.getSize().y) / 2);
		shell.setMaximized(maximized);
		shell.open();
		while (!shell.isDisposed()) {
			BusyIndicator.showWhile(display, new Runnable() {

				public void run() {
					boolean doit = false;
					do {
						doit = display.readAndDispatch();
					} while (doit);
				}
			});
			display.sleep();
		}

	}
	
	/**
	 * Método que devulve el estilo del componente DatePicker
	 * @return el estilo
	 */
	public static int getDatePickerStyle(){
		int dpStyle = 0 | 
//		DatePickerStyle.BUTTONS_ON_BOTTOM | 
	  DatePickerStyle.HIDE_WHEN_NOT_IN_FOCUS|
	  DatePickerStyle.DISABLE_MONTH_BUTTONS |
//	  DatePickerStyle.NO_TODAY_BUTTON |
	  DatePickerStyle.SINGLE_CLICK_SELECTION |
//	  DatePickerStyle.TEN_YEARS_BUTTONS |
	  DatePickerStyle.WEEKS_STARTS_ON_MONDAY|
	  DatePickerStyle.YEAR_BUTTONS |
	  0 ;
		
		return dpStyle;
	}
	

	
	/**
	 * Método que devulve el formateador de fechas para el componente DatePicker
	 * @return formateador de fechas
	 */
	public static SimpleDateFormat getDateFormatter(){
		SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
		return sdf;
	}
}
