package lu.tudor.santec.gecamed.formeditor.gui;

import java.awt.Color;
import java.awt.Component;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.File;
import java.io.InputStream;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.filechooser.FileFilter;
import javax.swing.text.JTextComponent;

import lu.tudor.santec.gecamed.core.gui.widgets.NamedComponent;
import lu.tudor.santec.gecamed.core.utils.FileUtils;
import lu.tudor.santec.gecamed.core.utils.ManagerFactory;
import lu.tudor.santec.gecamed.formeditor.ejb.entity.beans.Form;
import lu.tudor.santec.gecamed.formeditor.gui.view.FormTab;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.Incident;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.IncidentEntry;
import lu.tudor.santec.gecamed.patient.ejb.session.beans.IncidentManagerBean;
import lu.tudor.santec.gecamed.patient.ejb.session.interfaces.IncidentManager;
import lu.tudor.santec.gecamed.patient.gui.PatientManagerModule;

import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

public abstract class FormWidgets {
	
	private static Logger logger = Logger.getLogger(FormWidgets.class.getName());

    private static final String TEMP_DIR = System.getProperty("java.io.tmpdir");
    
	public static final String NEWLINE = System.getProperty("line.separator");
	public static final String FOLDER_SEPARATOR = System.getProperties().getProperty("file.separator"); //File.separatorChar;
	public static final char PATH_SEPARATOR = File.pathSeparatorChar;



	/* ======================================== */
	// 			FILE & STREAM OPERATIONS
	/* ======================================== */
	/**
	 * Loads a file of the jar-file, using the referencingClass, 
	 * the default sub folder "resources" and the file name.
	 * 
	 * @param referencingClass	the class, which folder is taken to get the file
	 * @param fileName	the name of the file to load
	 * @return	the file specified by the class and the name
	 */
	public static File getFile (Class<?> referencingClass, String fileName) {
		return getFile(referencingClass, "resources/", fileName);
	}
	

	/**
	 * Loads a file of the jar-file, using the referencingClass, 
	 * the given sub folder and the file name.
	 * 
	 * @param referencingClass	the class, which folder is taken to get the file
	 * @param folder	the folder to reach the file starting from the package of the 
	 * 					referencing class
	 * @param fileName	the name of the file to load
	 * @return	the file specified by the class and the name
	 */
	public static File getFile (Class<?> referencingClass, String folder, String fileName) {
	    /**
	     * copies the xsd File to a tmp dir and returns its location.
	     * @return
	     */
		File file = null;
		
		try {
		    file = new File(TEMP_DIR, fileName);
		    file.deleteOnExit();
		    FileUtils.writeStreamToFile(getFileAsStream(referencingClass, folder, fileName), file);
		} catch (Exception e){
			logger.log(Level.WARN, "Failed to copy into temp directory " + file , e);
		}
		return file;
	}
	
	/**
	 * @param referencingClass
	 * @param fileName
	 * @return
	 */
	public static InputStream getFileAsStream (Class<?> referencingClass, String fileName) {
		return getFileAsStream(referencingClass, "resources/", fileName);
	}
	
	/**
	 * @param referencingClass
	 * @param folder
	 * @param fileName
	 * @return
	 */
	public static InputStream getFileAsStream (Class<?> referencingClass, String folder, String fileName) {
		if (!folder.endsWith("/")) {
			folder += "/";
		}
		return referencingClass.getResourceAsStream(folder + fileName);
	}
	
	
	/**
	 * This creates a file filter, which filters all files with the given endings
	 * 
	 * @param allowedEndings 	the endings to filter for (endings are entered without the '.')
	 * @return	a javax.swing.filechooser.FileFilter for the specified endings
	 */
	public static FileFilter getFileFilter (final String ... allowedEndings) {
		return new FileFilter() {
			
			@Override
			public String getDescription() {
				StringBuffer description = new StringBuffer();
				for (String ending : allowedEndings) {
					if (description.length() > 0) {
						description.append(", ");
					}
					description.append(".").append(ending);
				}
				return null;
			}
			
			@Override
			public boolean accept(File f) {
				if (f.isDirectory()) {
					return true;
				}
				
				for (String ending : allowedEndings) {
					if (f.getName().endsWith(ending)) {
						return true;
					}
				}
				
				return false;
			}
		};
	}
	
	/* ======================================== */
	/* ======================================== */
	
	
	/* ======================================== */
	// 		BASE64 - BYTE CONVERTING
	/* ======================================== */
	
	/**
	 * Converts a byte array into a Base64 String.
	 * 
	 * @param bytes 
	 * The byte array, that shell be converted into a Base64 String.
	 * 
	 * @return
	 * Returns the Base64 String of the given byte array.
	 */
	public static String bytesToBase64 (byte[] bytes) {
		/* ======================================== */
		if (bytes == null) {
			return "";
		}
		
		String base64 = null;
		
		try {
			/* ---------------------------------------- */
			base64 = new String(Base64.encodeBase64(bytes));
			/* ---------------------------------------- */
		} catch (Exception e) {
			logger.log(Level.ERROR, e.getMessage(), e);
		}
		
		return base64;
		/* ======================================== */
	}
	
	
	/**
	 * Converts a Base64 String into a byte array.
	 * 
	 * @param base64
	 * The Base64 String to create the byte array.
	 * @return
	 * Returns the byte array that was generated out of the Base64 String.
	 */
	public static byte[] base64ToBytes (String base64) {
		/* ======================================== */
		byte[] bytes = null;
		
		try {
			/* ---------------------------------------- */
			bytes = Base64.decodeBase64(base64.getBytes());
			/* ---------------------------------------- */
		} catch (Exception e) {
			logger.log(Level.ERROR, e.getMessage(), e);
		}
		
		return bytes;
		/* ======================================== */
	}
	
	/* ======================================== */
	/* ======================================== */
	
	
	/* ======================================== */
	// 		DATE FORMATING
	/* ======================================== */
	private static DateFormat dateFormatter;
	private static DateFormat dateTimeFormatter;
	
	public static DateFormat getInternationalDateFormat () {
		if (dateFormatter == null) {
			dateFormatter = new SimpleDateFormat("yyyy-MM-dd");
			dateFormatter.setLenient(true);
		}
		return dateFormatter;
	}
	
	public static DateFormat getInternationalDateAndTimeFormat () {
		if (dateTimeFormatter == null) {
			dateTimeFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
			dateTimeFormatter.setLenient(true);
		}
		return dateTimeFormatter;
	}
	
	public static String formatAsInternationalDateAndTimeFormat (Date date) {
		return getInternationalDateAndTimeFormat().format(date);
	}
	
	public static String formatAsInternationalDateFormat (Date date) {
		return getInternationalDateFormat().format(date);
	}
	
	public static Date parseInternationalDateAndTimeFormat (String date) {
		try {
			return getInternationalDateAndTimeFormat().parse(date);
		} catch (ParseException e) {
			return null;
		}
	}
	
	public static Date parseToDate (String date, Locale locale) {
		DateFormat format = DateFormat.getDateInstance(DateFormat.DEFAULT, locale);
		return parseToDate(date, format);
	}
	
	public static Date parseToDate (String date, String pattern) {
		DateFormat format = new SimpleDateFormat(pattern);
		return parseToDate(date, format);
	}
	
	public static Date parseToDate (String date, DateFormat format) {
		try  {
			return format.parse(date);
		} catch (ParseException e) {
			return null;
		}
	}
	
	public static Date parseInternationalDateFormat (String date) {
		try {
			return getInternationalDateFormat().parse(date);
		} catch (ParseException e) {
			return null;
		}
	}
	
	/* ======================================== */
	/* ======================================== */
	
	
	/* ======================================== */
	// 		NUMBER PARSING
	/* ======================================== */
	
	
	/**
	 * Checks whether the given String can be parsed into a double value
	 * 
	 * @param number the String to be checked
	 * @return true if the String can be parsed, else false
	 */
	public static boolean isDouble (String number) {
		try {
			Double.parseDouble(number);
			return true;
		} catch (NumberFormatException e) {
			return false;
		}
	}
	
	/**
	 * Checks whether the given String can be parsed into a long value 
	 * 
	 * @param number the String to be checked
	 * @return true if the String can be parsed, else false
	 */
	public static boolean isLong (String number) {
		try {
			Long.parseLong(number);
			return true;
		} catch (NumberFormatException e) {
			return false;
		}
	}
	
	/**
	 * Parses the value into a Double value and returns it 
	 * or null if the String couldn't be parsed to Double.
	 * 
	 * @param number the String to be parsed
	 * @return the Double value of the String or null
	 */
	public static Double parseDouble (String number) {
		try {
			return Double.parseDouble(number);
		} catch (NumberFormatException e) {
			return null;
		}
	}
	

	/**
	 * Parses the value into a Long value and returns it 
	 * or null if the String couldn't be parsed to Long.
	 * 
	 * @param number the String to be parsed
	 * @return the Long value of the String or null
	 */
	public static Long parseLong (String number) {
		try {
			return Long.parseLong(number);
		} catch (NumberFormatException e) {
			return null;
		}
	}
	
	public static Float parseFloat (String number, Float ifError) {
		/* ======================================== */
		try {
			return Float.parseFloat(number);
		} catch (Exception e) {
			return ifError;
		}
		/* ======================================== */
	}
	
	public static Integer parseInt (String number, Integer ifError) {
		/* ======================================== */
		try {
			return Integer.parseInt(number);
		} catch (Exception e) {
			return ifError;
		}
		/* ======================================== */
	}
	
	public static Boolean parseBool (String bool, Boolean ifError) {
		/* ======================================== */
		try {
			return Boolean.parseBoolean(bool);
		} catch (Exception e) {
			return ifError;
		}
		/* ======================================== */
	}
	
	/* ======================================== */
	/* ======================================== */
	
	
	/* ======================================== */
	// 		COMPONENT OPERATIONS
	/* ======================================== */
	
	
	/**
	 * Adds a mouse listener, which focuses the text component, if clicking the JComponent. 
	 * 
	 * @param label	The JComponent, to be clicked
	 * @param focusableComponent	The TextComponent to be focused
	 */
	public static void requestFocusForComponent (JComponent clickedComponent, final JTextComponent focusableComponent) {
		/* ======================================== */
		clickedComponent.addMouseListener(new MouseListener() {
			
			private void focusTextComponent () {
				focusableComponent.requestFocus();
			}
			
			public void mouseReleased(MouseEvent e) {
				focusTextComponent();
			}
			
			public void mouseClicked(MouseEvent e) {
				focusTextComponent();
			}
			
			public void mouseExited(MouseEvent e) {}
			public void mousePressed(MouseEvent e) {}
			public void mouseEntered(MouseEvent e) {}
		});
		/* ======================================== */
	}
	
	
	
	/**
	 * Gets the underlying JPanel of the NamedComponent and sets its visibility to <b>visibility</b>
	 * 
	 * @param component the component in the NamedComponent
	 * @param visibility the visibility of the NamedComponent
	 */
	public static void setNamedComponentVisibility (Component component, boolean visibility) {
		/* ======================================== */
		while (component != null) {
			/* ---------------------------------------- */
			if (component instanceof NamedComponent) {
				component.setVisible(visibility);
				return;
			} else {
				component = component.getParent();
			}
			/* ---------------------------------------- */
		}
		/* ======================================== */
	}
	
	/**
	 * This methods sets all Components a panel contains and their components (if it is a JPanel) enabled(<b>enable</b>)
	 * 
	 * @param panel the panel whose components shell be dis-/enabled
	 * @param enable defines whether the components shell be disabled or enabled
	 */
	public static void enableAllComponentsOfPanel (JPanel panel, boolean enable) {
		/* ======================================== */
		for (Component c : panel.getComponents()) {
			if (c instanceof JPanel) {
				enableAllComponentsOfPanel((JPanel) c, enable);
			} else {
				c.setEnabled(enable);
			}
		}
		/* ======================================== */
	}
	
	
	/* ======================================== */
	/* ======================================== */
	
	
	
	/* ======================================== */
	// 			MISC
	/* ======================================== */
	
	
//	public static void arrayToList (Object[] source, Collection<?> target) {
//		target.clear();
//		
//		for (Object o : source) {
//			target.add(o);
//		}
//	}

	
//	public static Object[] listToArray (Collection<?> source) {
//		// TODO: ...
////		int i = 0;
////		for (T o : source) {
////			target[i++] = o;
////		}
//		
//		return source.toArray();
//	}
	
	
	/* ======================================== */
	/* ======================================== */
	
	// for testing ...
//	public static void main(String[] args) {
//		/* ======================================== */
//		String[] source = {"a", "b", "c"};
//		LinkedList<String> target = new LinkedList<String>();
//		
//		arrayToList(source, target);
//		
//		System.out.println(target);
//		/* ======================================== */
//	}
	
	public static JTextArea getFormatedTextArea (String text) 
	{
		JTextArea textArea = new JTextArea();
		textArea.setText(text);
		textArea.setEnabled(false);
		textArea.setOpaque(false);
		textArea.setDisabledTextColor(Color.black);
		textArea.setLineWrap(true);
		textArea.setWrapStyleWord(true);
		textArea.setCaretPosition(0);
		
		return textArea;
	}
	
	
	/**
	 * Creates a new IncidentEntry from the current incident and adds it to form.
	 */
	public static Form addFormEntryForCurrentIncident (Form form) 
	{
		Incident inc = PatientManagerModule.getInstance()
    			.getPatientPanel().getCurrentIncident();
		IncidentManager incidentManager = (IncidentManager) ManagerFactory
				.getRemote(IncidentManagerBean.class);
		
		IncidentEntry ie = new IncidentEntry();
		ie.setIncident(inc);
		ie = incidentManager.saveEntry(ie, FormTab.NAME);
		form.setIncidentEntry(ie);
		
		return form;
	}
}
